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

    string public LASD_PROVENANCE = "";

    uint256 public startingIndexBlock;

    uint256 public startingIndex;

    uint256 public monsterPrice = 200000000000000000000; //the minimum amount of saletoken required to buy MonstaNFTs

    uint256 public MAX_monster;

    bool public saleIsActive = false;

    bool public whiteListIsActive = false;

    IERC20 public saletoken;

    bytes32 treeRoot;
    
   address walletToRecieveToken=0x000000000000000000000000000000000000dEaD;

    uint256 public REVEAL_TIMESTAMP;

    event minted(address indexed to, uint256 indexed tokenId, string message);

    constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 saleStart)  ERC721(name, symbol) {
        MAX_monster = maxNftSupply;
        REVEAL_TIMESTAMP = saleStart + (86400 * 5);
    }

    function withdrawToken() public onlyOwner {
        uint balance = address(this).balance;
        msg.sender.transfer(balance);
        saletoken.transfer(msg.sender, saletoken.balanceOf(address(this)));
    }


    function setPurchaseToken(IERC20 token) public onlyOwner {
        saletoken = token;
    }

    function setNFTprice(uint newPrice) public onlyOwner {
        monsterPrice=newPrice;
    }

    function setWalletToRecieveToken(address deadwallet) public onlyOwner {
        walletToRecieveToken = deadwallet;
    }

    /**
     * Set monster aside
     */
    function reservemonster(uint256 _count) public onlyOwner {        
        uint supply = totalSupply();
        uint i;
        for (i = 0; i < _count; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }

    function setRevealTimestamp(uint256 revealTimeStamp) public onlyOwner {
        REVEAL_TIMESTAMP = revealTimeStamp;
    } 
    /*     
    * Set provenance once it's calculated
    */
    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        LASD_PROVENANCE = provenanceHash;
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        _setBaseURI(baseURI);
    }

    /*
    * Pause sale if active, make active if paused
    */
    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    function flipPreSaleState() public onlyOwner {
        whiteListIsActive=!whiteListIsActive;
    }

    // Merkle Verification

    function setRoot(bytes32 newRoot) public onlyOwner {
        treeRoot=newRoot;
    }

        function verify(
        bytes32 root,
        bytes32 leaf,
        bytes32[] memory proof
    )
        public
        pure
        returns (bool)
    {
        bytes32 computedHash = leaf;

        for (uint256 i = 0; i < proof.length; i++) {
        bytes32 proofElement = proof[i];

        if (computedHash < proofElement) {
            // Hash(current computed hash + current element of the proof)
            computedHash = keccak256(abi.encodePacked(computedHash, proofElement));
        } else {
            // Hash(current element of the proof + current computed hash)
            computedHash = keccak256(abi.encodePacked(proofElement, computedHash));
        }
        }

        // Check if the computed hash (root) is equal to the provided root
        return computedHash == root;
    }
    
    function claim(bytes32 leaf,bytes32[] memory proof) public view returns (bool){
    return verify(treeRoot, leaf, proof);
    }


    /**
    * Mints monster
    */
    function mintmonster( string calldata message) public {
        require(saleIsActive, "Sale must be active to mint monster");
        require((totalSupply()+1) <= MAX_monster, "Purchase would exceed max supply of monster");
        require(saletoken.allowance(msg.sender, address(this)) >= monsterPrice, "This contract is not approved to spend your token" );
        saletoken.approve(walletToRecieveToken, monsterPrice);
        saletoken.transferFrom(msg.sender, walletToRecieveToken, monsterPrice);
        
        uint mintIndex = totalSupply();
            if (totalSupply() < MAX_monster) {
                super._safeMint(msg.sender, mintIndex+1);
                super._setTokenURI(mintIndex+1,message);
                emit minted(msg.sender, mintIndex+1, message);
            }
        
        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_monster || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        } 
    }


    function whiteListMintMonster( bytes32 leaf, bytes32[] memory proof, string calldata message) public {
        require(whiteListIsActive, "Presale must be active to mint monster");
        require(claim(leaf,proof), "Presale must be active to mint monster");
        require((totalSupply()+1) <= MAX_monster, "Purchase would exceed max supply of monster");
        require(saletoken.allowance(msg.sender, address(this)) >= monsterPrice, "This contract is not approved to spend your token" );
        saletoken.approve(walletToRecieveToken, monsterPrice);
        saletoken.transferFrom(msg.sender, walletToRecieveToken, monsterPrice);
        
        uint mintIndex = totalSupply();
            if (totalSupply() < MAX_monster) {
                super._safeMint(msg.sender, mintIndex+1);
                super._setTokenURI(mintIndex+1,message);
                emit minted(msg.sender, mintIndex+1, message);
            }
        
        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_monster || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        } 
    }


    function giftmonster(address giftAddress, string calldata message) public onlyOwner {
        // require(saleIsActive, "Sale must be active to mint monster");
        require((totalSupply()+1) <= MAX_monster, "Purchase would exceed max supply of monster");
        require(saletoken.allowance(msg.sender, address(this)) >= monsterPrice, "This contract is not approved to spend your token" );
        saletoken.approve(walletToRecieveToken, monsterPrice);
        saletoken.transferFrom(msg.sender, walletToRecieveToken, monsterPrice);
        
        uint mintIndex = totalSupply();
            if (totalSupply() < MAX_monster) {
                super._safeMint(giftAddress, mintIndex+1);
                super._setTokenURI(mintIndex+1,message);
                emit minted(giftAddress, mintIndex+1, message);
            }
        
        // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
        // the end of pre-sale, set the starting index block
        if (startingIndexBlock == 0 && (totalSupply() == MAX_monster || block.timestamp >= REVEAL_TIMESTAMP)) {
            startingIndexBlock = block.number;
        } 
    }

    /**
     * Set the starting index for the collection
     */
    function setStartingIndex() public {
        require(startingIndex == 0, "Starting index is already set");
        require(startingIndexBlock != 0, "Starting index block must be set");
        
        startingIndex = uint(blockhash(startingIndexBlock)) % MAX_monster;
        // 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(startingIndexBlock) > 255) {
            startingIndex = uint(blockhash(block.number - 1)) % MAX_monster;
        }
        // Prevent default sequence
        if (startingIndex == 0) {
            startingIndex = startingIndex.add(1);
        }
    }

    /**
     * Set the starting index block for the collection, essentially unblocking
     * setting starting index
     */
    function emergencySetStartingIndexBlock() public onlyOwner {
        require(startingIndex == 0, "Starting index is already set");
        
        startingIndexBlock = block.number;
    }
}
/**
 * @dev Extends ERC721 Non-Fungible Token Standard basic implementation
 */
function emergencySetStartingIndexBlock() public onlyOwner {
    require(startingIndex == 0, "Starting index is already set");
        
    startingIndexBlock = block.number;
}

/**
 * Set the starting index block for the collection, essentially unblocking
 * setting starting index
 */