contract Anon is Ownable, ERC721 {
    using SafeMath for uint256;

    /**
     Tile related configurations
     */

    // Token IDs are from 0 to 10132
    uint256 internal constant ALL_TOKEN_NUM = 10133;

    /**
     Draw related configurations
     */
    
    // proof of state of all tiles
    string public constant SHA256 = "253762fe521a835df8726d8c614f0f7bbb86e67690b422e0c3556d7b4d5e146d";

    // Have 3 different draws
    uint256 internal constant ALL_DRAW_NUM = 3;

    // can draw 1 token, 5 tokens or 10 tokens together
    enum DrawLevel {One, Five, Ten}

    struct Draw {
        DrawLevel level;
        // the total price
        uint256 price;
        // how many tokens to get
        uint256 number;
    }

    Draw[ALL_DRAW_NUM] internal allDraws;

    /**
     Draw & Mint related stats
     */

    // how many tokens are sold
    uint256 internal soldNumber;

    // The user actions are enabled or not, default true
    bool internal enabled = true;

    // withdraw splits
    uint256 internal constant ALL_SLICE_NUM = 6;

    struct Slice {
        string name;
        address to;
        uint256 share; // percentage
    }

    Slice[ALL_SLICE_NUM] internal slices;

    // gift fund
    uint256 internal giftFund; // the remaining fund needs to send to giftFundAddress

    address internal giftFundAddress;

    /**
     Events
     */

    // Emit the event when user play the game
    event PlayRequestReceived(
        address indexed toAddress,
        uint256 indexed gameId,
        uint256 indexed encrypted
    );

    // Emit the event when user draw
    event TokenDrawn(string indexed uuid, uint256[] tokens);

    /**
     constructor
     */

    constructor(
        string memory _name,
        string memory _symbol
    ) public ERC721(_name, _symbol) {
        // init draw conf
        allDraws[0] = Draw({level: DrawLevel.One, price: 0.1 ether, number: 1});
        allDraws[1] = Draw({
            level: DrawLevel.Five,
            price: 0.45 ether,
            number: 5
        });
        allDraws[2] = Draw({level: DrawLevel.Ten, price: 0.8 ether, number: 10});

        // init withdraw splits
        slices[0] = Slice({name: "Anna", to: address(0x31ee1e6A5adf01d86CB5835c1FC08b8D3D547b4e), share: 27});
        slices[1] = Slice({name: "Elsa", to: address(0x8ee2DAdEfd72f17e35FDC85136C244f7EE05Dfa5), share: 20});
        slices[2] = Slice({name: "Mickey", to: address(0xa652b59F2b685564563556B54F8aD1F8Bec9Ea7b), share: 20});
        slices[3] = Slice({name: "Belle", to: address(0x0Cc7eb98B12E8483C3a5d64995d1543B22a9a9dB), share: 20});
        slices[4] = Slice({name: "Dumbo", to: address(0xb680073B79eFFcdf6F599B5614DCAC54568612fa), share: 12});
        slices[5] = Slice({name: "Goofy", to: address(0xE10012bC19838f43ee54CfD6a44293A2667b7F44), share: 1});

        // init gift funds
        giftFund = 60 ether;
        giftFundAddress = address(0x31C423e089EdF3748697408456EfA98836816AA7);
    }

    /**
     user facing functions
     */

    modifier onlyWhenEnabled() {
        require(enabled, "Contract is disabled, please come back later");
        _;
    }

    // user call this function to draw
    function draw(string memory uuid) public onlyWhenEnabled payable {
        uint256 number;
        for (uint256 i = 0; i < ALL_DRAW_NUM; i++) {
            if (allDraws[i].price == msg.value) {
                number = allDraws[i].number;
                break;
            }
        }

        require(
            number > 0,
            "Invalid price, please pay either 0.1 ETH for 1 draw or 0.45 ETH for 5 draws or 0.8 ETH for 10 draws"
        );

        uint256 newSoldNumber = soldNumber + number;

        require(newSoldNumber <= ALL_TOKEN_NUM, "Not enough supply");

        uint256[] memory tokens = new uint256[](number);

        for (uint256 i = 0; i < number; i++) {
            // tokenId is taken sequential
            uint256 tokenId = soldNumber + i;
            // mint the token immediately
            _mint(msg.sender, tokenId);

            tokens[i] = tokenId;
        }

        // update the sold number
        soldNumber = newSoldNumber;

        // Emit token drawn event
        emit TokenDrawn(uuid, tokens);
    }

    // user call this function to play
    function play(uint256 gameId, uint256 encrypted)
        public
        onlyWhenEnabled
    {
        emit PlayRequestReceived(msg.sender, gameId, encrypted);
    }

    /**
     OpenSea facing functions
     */

    function contractURI() public view returns (string memory) {
        string memory base = baseURI();

        // If there is no base URI, return the token URI.
        if (bytes(base).length == 0) {
            return "";
        }
        
        return string(abi.encodePacked(base, "opensea-metadata"));
    }

    /**
     admin facing functions
     */

    function setEnabled(bool _enabled) public onlyOwner {
        enabled = _enabled;
    }

    function _payout(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}("");
        require(success, "Payout failed");
    }

    function withdraw() public onlyOwner {
        uint256 amount = address(this).balance;

        // always payout to gift fund first
        if (giftFund > 0) {
            uint256 value;
            
            if (amount > giftFund) {
                value = giftFund;
                giftFund = 0;
            } else {
                value = amount;
                giftFund -= amount;
            }

            _payout(giftFundAddress, value);
            return;
        }

        // payout to slices if gift fund has already paid out
        uint256 paid = 0;
        // loop except the last one
        for (uint256 i = 0; i < ALL_SLICE_NUM - 1; i++) {
            uint256 value = amount * slices[i].share / 100;
            
            _payout(slices[i].to, value);

            paid += value;
        }

        // payout the remaining to the last slice
        //  - sum of percentages is 100, paying the remaining fund is the same
        //  - paying the remaining to avoid potential rounding bug
        _payout(slices[ALL_SLICE_NUM - 1].to, amount - paid);
    }

    function setBaseURI(string memory baseURI_) public onlyOwner {
        _setBaseURI(baseURI_);
    }
}
function setEnabled(bool _enabled) public onlyOwner {
    enabled = _enabled;
}

/**
 admin facing functions
 */