contract CryptoBall645 is ERC721Full("Crypto balls 6/45", "B645"), Ownable, Referrers {
    using Balance for Balance.BalanceStorage;
    Balance.BalanceStorage balance;

    event OwnerBonus(address indexed _to, uint _amount);
    event ReferrerBonus(address indexed _to, uint _amount);

    using SafeMath for uint256;

    uint constant public MAX_BALLS_COUNT = 6;
    uint constant public MAX_BALL_NUMBER = 45;

    //Bonus of contract owner
    uint public ownerBonusSale = 25;
    uint public ownerBonusWin = 0;

    uint public ticketPrice = 0.01 ether;

    uint public tokenIds = 0;
    mapping(uint => uint) public mintDate;

    DrawContractI drawContract;
    address public drawContractAddress;

    modifier onlyDrawContract() {
        require(msg.sender == drawContractAddress);
        _;
    }

    modifier allowBuyTicket() {
        require(msg.value == ticketPrice);
        _;
    }

    modifier allowBuyTicketCount(uint _drawCount) {
        require(msg.value == ticketPrice*_drawCount);
        _;
    }

    modifier allowRegisterTicket(uint[] _numbers) {
        require(_numbers.length == MAX_BALLS_COUNT);
        for (uint i = 0; i < MAX_BALLS_COUNT; i++) {
            require(_numbers[i] > 0);
            require(_numbers[i] <= MAX_BALL_NUMBER);
            for (uint t = 0; t < MAX_BALLS_COUNT; t++) {
                if (t == i) {
                    continue;
                }
                require(_numbers[t]!=_numbers[i]);
            }
        }
        _;
    }

    /**
      * @dev Guarantees msg.sender is owner of the given token
      * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender
      */
    modifier onlyOwnerOf(uint256 _tokenId) {
        require(ownerOf(_tokenId) == msg.sender);
        _;
    }

    // Update ticket price
    function setTicketPrice(uint _amount) external onlyOwner {
        ticketPrice = _amount;
    }

    // Update sale bonus for owner
    function setOwnerBonusSale(uint _amount) external onlyOwner {
        ownerBonusSale = _amount;
    }

    // Update win bonus for owner
    function setOwnerBonusWin(uint _amount) external onlyOwner {
        ownerBonusWin = _amount;
    }

    function setDrawContract(address _address) public onlyOwner {
        drawContractAddress = _address;
        drawContract = DrawContractI(_address);
    }

    function burn(uint _tokenId) external onlyOwner {
        require((now - mintDate[_tokenId]) / 60 / 60 / 24 > 30);
        require(drawContract.ticketToDraw(_tokenId) == 0);
        uint _refundPrice = ticketPrice * (100 - ownerBonusSale - referrerBonusSale) / 100;
        balance._addBalance(owner(), _refundPrice);
        _burn(ownerOf(_tokenId), _tokenId);
    }

    // Tokens list of user
    function getMyTickets() public view returns (uint256[]) {
        uint _myTotalTokens = balanceOf(msg.sender);
        uint[] memory _myTokens = new uint[](_myTotalTokens);
        uint k = 0;
        for (uint i = 0; i <= totalSupply(); i++) {
            uint _myTokenId = tokenOfOwnerByIndex(msg.sender, i);
            if (_myTokenId > 0) {
                _myTokens[k] = _myTokenId;
                k++;
                if (k >= _myTotalTokens) {
                    break;
                }
            }
        }
        return _myTokens;
    }

    // Buy ticket
    function _buyToken(address _to, uint _referrerId) private returns (uint) {
        tokenIds = tokenIds.add(1);
        uint _tokenId = tokenIds;
        _mint(_to, _tokenId);
        transferFrom(ownerOf(_tokenId), msg.sender, _tokenId);
        mintDate[_tokenId] = now;
        // Send bonus to the owner
        _addOwnerBonus(ticketPrice, ownerBonusSale);
        // Send bonus to the referrer if referrerId was sent and referrer was registered
        if (_referrerId == 0) {
            _referrerId = addressToReferrer[owner()];
        }
        if (_referrerId > 0 && referrerToAddress[_referrerId] > 0) {
            _addReferrerBonus(_referrerId, ticketPrice, referrerBonusSale);
            _setTicketReferrer(_tokenId, _referrerId);
        }
        return _tokenId;
    }

    function() public payable {
        require(msg.value >= ticketPrice);
        uint ticketsCount = msg.value / ticketPrice;
        uint returnCount = msg.value % ticketPrice;

        if (returnCount > 0) {
            msg.sender.transfer(returnCount);
        }
        for (uint i = 1; i <= ticketsCount; i++) {
            _buyToken(msg.sender, 0);
        }
    }

    // User buy new ticket
    function buyTicket(uint _referrerId) public payable allowBuyTicket {
        _buyToken(msg.sender, _referrerId);
    }

    // Buy, fill and register ticket
    function buyAndRegisterTicket(uint _referrerId, uint[] _numbers, uint _drawCount) public payable allowBuyTicketCount(_drawCount) allowRegisterTicket(_numbers) returns (uint){
        uint _drawNumber = drawContract.drawNumber();
        for (uint i = 0; i<_drawCount; i++) {
            uint _tokenId = _buyToken(msg.sender, _referrerId);
            uint _draw = _drawNumber + i;
            drawContract.registerTicket(_tokenId, _numbers, ticketPrice, ownerBonusSale, referrerBonusSale, _draw);
        }
    }

    // Register ticket to the nearest drawing
    function registerTicket(uint _tokenId, uint[] _numbers, uint _drawNumber) public onlyOwnerOf(_tokenId) allowRegisterTicket(_numbers) {
        //Ticket not yet filled
        require(drawContract.ticketToDraw(_tokenId) == 0);
        drawContract.registerTicket(_tokenId, _numbers, ticketPrice, ownerBonusSale, referrerBonusSale, _drawNumber);
    }

    function _checkTicket(uint _tokenId, address _receiver) private returns (bool) {
        drawContract.checkTicket(_tokenId);
        uint _prize = drawContract.ticketPrizes(_tokenId);
        if (_prize > 0) {
            if (_prize == ticketPrice) {
                _buyToken(_receiver, ticketToReferrer[_tokenId]);
                balance._subBalance(owner(), ticketPrice);
            } else {
                _addPriceToBalance(_tokenId, _prize, _receiver);
            }
        }
        return true;
    }

    // Check ticket and move prize to balance
    function checkTicket(uint _tokenId) public {
        require(_exists(_tokenId));
        require(_checkTicket(_tokenId, ownerOf(_tokenId)));
    }

    // Contract owner can withdraw prizes after 30 days if winner didn't withdraw it
    function withdrawTicketPrize(uint _tokenId) public onlyOwner {
        require(_exists(_tokenId));
        uint _ticketDraw = drawContract.ticketToDraw(_tokenId);
        require((now - drawContract.drawTime(_ticketDraw)) / 60 / 60 / 24 > 30);
        require(_checkTicket(_tokenId, owner()));
    }

    function _addPriceToBalance(uint _tokenId, uint _prizeAmount, address _receiver) private {
        uint _ownerBonus = Utils.getBonusAmount(_prizeAmount, ownerBonusWin);
        uint _referrerBonus = Utils.getBonusAmount(_prizeAmount, referrerBonusWin);
        uint _referrerId = ticketToReferrer[_tokenId];
        uint winnerPrizeAmount = _prizeAmount - _ownerBonus - _referrerBonus;
        balance._addBalance(_receiver, winnerPrizeAmount);
        _addReferrerBonus(_referrerId, winnerPrizeAmount, referrerBonusWin);
        _addOwnerBonus(winnerPrizeAmount, ownerBonusWin);
    }

    // Get My balance
    function getMyBalance() public view returns (uint){
        return balance._getBalance(msg.sender);
    }

    // Withdraw My balance
    function withdrawMyBalance() public {
        uint _userBalance = balance._getBalance(msg.sender);
        require(_userBalance > 0);
        require(address(this).balance >= _userBalance);
        balance._clearBalance(msg.sender);
        msg.sender.transfer(_userBalance);
        emit Transfer(this, msg.sender, _userBalance);
    }

    function withdrawBalanceAmount(uint _amount) public {
        uint _userBalance = balance._getBalance(msg.sender);
        require(_userBalance > 0);
        require(_amount <= _userBalance);
        require(address(this).balance >= _amount);
        balance._subBalance(msg.sender, _amount);
        msg.sender.transfer(_amount);
        emit Transfer(this, msg.sender, _amount);
    }

    function _addReferrerBonus(uint _referrer, uint _fromAmount, uint _bonusPercent) internal {
        address _referrerAddress = referrerToAddress[_referrer];
        if (_referrerAddress == address(0)) {
            _referrerAddress = owner();
        }
        uint _amount = Utils.getBonusAmount(_fromAmount, _bonusPercent);
        if (_amount > 0) {
            balance._addBalance(_referrerAddress, _amount);
            totalEarnReferrer[_referrer] = totalEarnReferrer[_referrer].add(_amount);
            emit ReferrerBonus(_referrerAddress, _amount);
        }
    }

    function _addOwnerBonus(uint _fromAmount, uint _bonusPercent) internal {
        uint _amount = Utils.getBonusAmount(_fromAmount, _bonusPercent);
        if (_amount > 0) {
            balance._addBalance(owner(), _amount);
            emit OwnerBonus(owner(), _amount);
        }
    }

    function addBalance(address _address, uint _amount) external onlyDrawContract {
        balance._addBalance(_address, _amount);
    }

    function addSuperPrize() public payable {
        drawContract.addSuperPrize(msg.value);
    }
}
function getMyBalance() public view returns (uint){
    return balance._getBalance(msg.sender);
}

// Get My balance