contract FunkyMonkeyFratHouse is ERC721Burnable, Ownable {
    using ECDSA for bytes32;
    
    uint256 constant public PRICE = 0.0001 ether;
    uint256 constant public TOTAL_SUPPLY = 9999;
    uint256 public maxMintPerTransaction = 1000;
    
    string public placeholderURI = '';

    // 0 = Toga Party
    // 1 = Greek Life
    // 2 = Pirate Parade
    struct Drops {
        uint256 fromIndex;
        uint256 toIndex;
        uint256 totalAmount;
        uint256 startingIndex;
        uint256 startingIndexBlock;
        bool dropIsActive;
    }
    mapping (uint256 => Drops) public drops;
    
    address payable treasury;

    bool public publicSaleIsActive = false;

    constructor(address payable _treasury) public ERC721("Funky Monkey Frat House", "FMFH") {
        treasury = _treasury;
        
        setBaseURI('ipfs://QmYB4N3aF7AvyycK7Dde4cjDEgMSYpRGRpmqebax46zbae/');
        placeholderURI = 'ipfs://QmeC2JKPDjrPq4SYx8ZsJdzVvRBQBb7yecPv2JzeE1U2y7/';
    }

    /**
    * Initialize drop config for the toga party
    */
    function initDropConfigTogaParty(
        uint256 _fromIndex,
        uint256 _toIndex,
        uint256 _totalAmount,
        bool _activateStartingIndex,
        bool _dropIsActive
    ) external onlyOwner {
        require(_toIndex > _fromIndex, "FunkyMonkeyFratHouseClub: The _toIndex can't be smaller than the _fromIndex");
        if(_activateStartingIndex) {
            require(drops[0].startingIndex == 0, "FunkyMonkeyFratHouseClub: Starting index is already set");
            require(drops[0].startingIndexBlock != 0, "FunkyMonkeyFratHouseClub: Starting index block must be set");
            drops[0].startingIndex = uint(blockhash(drops[0].startingIndexBlock)) % _totalAmount;
            // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
            if (block.number.sub(drops[0].startingIndexBlock) > 255) {
                drops[0].startingIndex = uint(blockhash(block.number - 1)) % _totalAmount;
            }
        }

        drops[1].dropIsActive = false;
        drops[2].dropIsActive = false;
        drops[0] = Drops({
            fromIndex: _fromIndex,
            toIndex: _toIndex,
            totalAmount: _totalAmount,
            startingIndex: drops[0].startingIndex,
            startingIndexBlock: drops[0].startingIndexBlock,
            dropIsActive: _dropIsActive
        });
    }

    /**
    * Initialize drop config for the toga party
    */
    function initDropConfigGreekLife(
        uint256 _fromIndex,
        uint256 _toIndex,
        uint256 _totalAmount,
        bool _activateStartingIndex,
        bool _dropIsActive
    ) external onlyOwner {
        require(_toIndex > _fromIndex, "FunkyMonkeyFratHouseClub: The _toIndex can't be smaller than the _fromIndex");
        if(_activateStartingIndex) {
            require(drops[1].startingIndex == 0, "FunkyMonkeyFratHouseClub: Starting index is already set");
            require(drops[1].startingIndexBlock != 0, "FunkyMonkeyFratHouseClub: Starting index block must be set");
            drops[1].startingIndex = uint(blockhash(drops[1].startingIndexBlock)) % _totalAmount;
            // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
            if (block.number.sub(drops[1].startingIndexBlock) > 255) {
                drops[1].startingIndex = uint(blockhash(block.number - 1)) % _totalAmount;
            }
        }

        drops[0].dropIsActive = false;
        drops[2].dropIsActive = false;
        drops[1] = Drops({
            fromIndex: _fromIndex,
            toIndex: _toIndex,
            totalAmount: _totalAmount,
            startingIndex: drops[1].startingIndex,
            startingIndexBlock: drops[1].startingIndexBlock,
            dropIsActive: _dropIsActive
        });
    }

    /**
    * Initialize drop config for the Pirate Parada
    */
    function initDropConfigPirateParade(
        uint256 _fromIndex,
        uint256 _toIndex,
        uint256 _totalAmount,
        bool _activateStartingIndex,
        bool _dropIsActive
    ) external onlyOwner {
        require(_toIndex > _fromIndex, "FunkyMonkeyFratHouseClub: The _toIndex can't be smaller than the _fromIndex");
        if(_activateStartingIndex) {
            require(drops[2].startingIndex == 0, "FunkyMonkeyFratHouseClub: Starting index is already set");
            require(drops[2].startingIndexBlock != 0, "FunkyMonkeyFratHouseClub: Starting index block must be set");
            drops[2].startingIndex = uint(blockhash(drops[2].startingIndexBlock)) % _totalAmount;
            // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
            if (block.number.sub(drops[2].startingIndexBlock) > 255) {
                drops[2].startingIndex = uint(blockhash(block.number - 1)) % _totalAmount;
            }
        }

        drops[0].dropIsActive = false;
        drops[1].dropIsActive = false;
        drops[2] = Drops({
            fromIndex: _fromIndex,
            toIndex: _toIndex,
            totalAmount: _totalAmount,
            startingIndex: drops[2].startingIndex,
            startingIndexBlock: drops[2].startingIndexBlock,
            dropIsActive: _dropIsActive
        });
    }

    /**
    * Flip the sale state
    */
    function flipSale(bool _isActive) public onlyOwner {
        publicSaleIsActive = _isActive;
    }

    /**
    * Mint public FunkyMonkeyFratHouse
    */
    function mintFunkyMonkeyFratHouse(uint numberOfTokens) public payable {
        require(publicSaleIsActive, "FunkyMonkeyFratHouseClub: Public sale must be active to mint");
        require(totalSupply().add(numberOfTokens) <= TOTAL_SUPPLY, "FunkyMonkeyFratHouseClub: Exceeds max supply");
        require(numberOfTokens <= maxMintPerTransaction, "FunkyMonkeyFratHouseClub: Above max tx count");
        require(PRICE.mul(numberOfTokens) <= msg.value, "FunkyMonkeyFratHouseClub: ETH sent is incorrect");

        uint256 totalTokensAfterMint = totalSupply().add(numberOfTokens);
        if (drops[0].dropIsActive) {
            require(totalTokensAfterMint <= drops[0].totalAmount, "Can't mint more than the totalAmount per drop");
            if (totalTokensAfterMint == drops[0].totalAmount) {
                publicSaleIsActive = false;
            }
        } else if (drops[1].dropIsActive) {
            require(totalTokensAfterMint <= drops[1].totalAmount, "Can't mint more than the totalAmount per drop");
            if (totalTokensAfterMint == drops[1].totalAmount) {
                publicSaleIsActive = false;
            }
        } else if (drops[2].dropIsActive) {
            require(totalTokensAfterMint <= drops[2].totalAmount, "Can't mint more than the totalAmount per drop");
            if (totalTokensAfterMint == drops[2].totalAmount) {
                publicSaleIsActive = false;
            }
        }

        for(uint i = 0; i < numberOfTokens; i++) {
            uint mintIndex = totalSupply();
            _safeMint(msg.sender, mintIndex);
        }
        
        // 0 = Toga Party
        // 1 = Greek Life
        // 2 = Pirate Parade
        // Set the starting index if the drop is active
        if(drops[0].startingIndexBlock == 0 && drops[0].dropIsActive) {
            drops[0].startingIndexBlock = block.number;
        }
        // If we haven't set the starting index yet.
        if(drops[1].startingIndexBlock == 0 && drops[1].dropIsActive) {
            drops[1].startingIndexBlock = block.number;
        }
        // If we haven't set the starting index yet.
        if(drops[2].startingIndexBlock == 0 && drops[2].dropIsActive) {
            drops[2].startingIndexBlock = block.number;
        }
    }

    /**
    * Set the baseURI
    */
    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    /**
    * Set the MAX mints per TX
    */
    function setMaxMintsPerTransaction(uint256 _maxMintPerTransaction) public onlyOwner {
        maxMintPerTransaction = _maxMintPerTransaction;
    }

    /**
    * Set the placeholder URI
    */
    function setPlaceholderURI(string memory _placeholderURI) public onlyOwner {
        placeholderURI = _placeholderURI;
    }

    /**
    * Set the address of the treasury
    */
    function setTreasuryWallet(address payable _treasury) public onlyOwner {
        treasury = _treasury;
    }

    /**
    * Claim eth only available for the treasury address
    */
    function claimETH() public onlyTreasury(treasury) {
        payable(address(treasury)).transfer(address(this).balance);
    }

    /**
    * Get your api url based on your tokenId
    */
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        Drops memory drop;
        if(tokenId >= drops[0].fromIndex && tokenId <= drops[0].toIndex) {
            drop = drops[0];
        } else if(tokenId >= drops[1].fromIndex && tokenId <= drops[1].toIndex) {
            drop = drops[1];
        } else if(tokenId >= drops[2].fromIndex && tokenId <= drops[2].toIndex) {
            drop = drops[2];
        }

        if(drop.startingIndex == 0) {
            return placeholderURI;
        }

        uint256 moddedId = drop.fromIndex + ((tokenId + drop.startingIndex) % (drop.fromIndex + drop.totalAmount));
        return super.tokenURI(moddedId);
    }
}
function setMaxMintsPerTransaction(uint256 _maxMintPerTransaction) public onlyOwner {
    maxMintPerTransaction = _maxMintPerTransaction;
}

/**
 * Set the MAX mints per TX
 */