contract MechNFT is ERC721URIStorage, VRFConsumerBase, Ownable {
        
    // Enum representing mint status
    enum MintPhase {
        Closed,
        Whitelist,
        Open
    }

    MintPhase public liveStatus;

    event requestProtoMech(uint256 indexed tokenId);
    event receivedChainlinkVRF(bytes32 indexed requestId, uint256 randomNumber);
    event executedReveal(uint256 mechsRevealed);
    
    string public baseURI;

    address public constant whitelistSigner = 0x21aE3B48BA6876D9298E37F0A56c8d51339C6e74;
    address public constant proxyRegistryAddress = 0xa5409ec958C83C3f309868babACA7c86DCB077c1; // Approving Opensea Registry for lower secondary gas
   
    uint256 public tokenCounter;
    uint256 public revealedMechs;
    uint256 public maxMint;
    uint256 internal fee;
    uint256 public rngSeed;

    uint256 public constant price = 0.08 ether; 
    uint256 public constant maxSupply = 10001; // Adding one to max supply to avoid LTE operator

    bytes32 internal keyHash;

    bool public mutableMetadata;
    
    mapping(address => uint256) addressToNumberOfTokensMinted;

    constructor()
        VRFConsumerBase(0xf0d54349aDdcf704F77AE15b96510dEA15cb7952, 0x514910771AF9Ca656af840dff83E8264EcF986CA)
        ERC721("MechNFT", "MECH")
    {
        //update for launch
        liveStatus = MintPhase.Closed;
        tokenCounter = 0;
        mutableMetadata = true;
        revealedMechs = 0;
        keyHash = 0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445; // ChainLink keyHash
        baseURI = "https://gateway.pinata.cloud/ipfs/QmaorAspp4UDE2eB6hxUeT4P2N7rmqR6KU1t4XtcDuagJD/Mech_";
        fee =   2* 10 ** 18; //      Ethereum Mainnet Price
        maxMint = 3; // Contract adds one to maxMint to avoid using LTE operator for lower gas

    }

    //Withdraw funds to owner.
    function withdraw() public payable onlyOwner {
        payable(owner()).transfer(address(this).balance);
    }

    //Allows owner to permanently freeze metadata URI - ownerAddress can send freezeMetadata(TRUE) to freeze the data.
    function freezeMetadata(bool _freeze) public onlyOwner {
        mutableMetadata = mutableMetadata && !_freeze;
    }

    //int to string function.
    function uint2str(uint256 _i)
        internal
        pure
        returns (string memory _uintAsString)
    {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len;
        while (_i != 0) {
            k = k - 1;
            uint8 temp = (48 + uint8(_i - (_i / 10) * 10));
            bytes1 b1 = bytes1(temp);
            bstr[k] = b1;
            _i /= 10;
        }
        return string(bstr);
    }

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (tokenId < revealedMechs) {
            string memory _token = string(abi.encodePacked(uint2str(tokenId+1)));
            return string(abi.encodePacked(baseURI, _token, ".json"));
        }
        return string(abi.encodePacked(baseURI, "0.json"));
    }

    //OwnerMinting Function. The team is reserving 100 Mechs from the 10,000 collection for promotional purposes and internal rewards.
    function ownerMint(uint256 tokenQuant) public payable virtual onlyOwner {
        
        require(
            tokenCounter + tokenQuant < maxSupply,
            "Would exceed max supply!"
        );

        for (uint256 i; i < tokenQuant; i++) {
            _mint(msg.sender, tokenCounter);
            tokenCounter = tokenCounter + 1;
        }

    }

    //Function is named to generate a Method ID of 0x00007f6c to save gas
    function whitelistMint_rhh(uint256 tokenQuant, bytes memory _whitelistToken)
        public
        payable
        virtual
    {
        
        require(
            (liveStatus == MintPhase.Whitelist) &&
                (whitelistSigner == recoverSigner_94g(_whitelistToken)),
            "Either Whitelist Phase is not live or your Whitelist Code is invalid!"
        );

        require(
            tokenCounter + tokenQuant < maxSupply,
            "Would exceed max supply"
        );

        require(
            msg.value == price * tokenQuant,
            "Wrong amount of ETH sent - please check price!"
        );

        require(
            addressToNumberOfTokensMinted[msg.sender] + tokenQuant < maxMint,
            "Minting this many tokens takes you over your maximum amount!"
        );

        // Increasing minted count in dictionary
        addressToNumberOfTokensMinted[msg.sender] += tokenQuant;

        for (uint256 i; i < tokenQuant; i++) {
            _mint(msg.sender, tokenCounter);
            tokenCounter = tokenCounter + 1;
        }
    }

    // Separating mint phases to reduce operations
    function publicMint_1VS(uint256 tokenQuant) public payable virtual {
        require(
            liveStatus == MintPhase.Open,
            "Mint Phase is not open to the public yet!"
        );

        require(
            tokenCounter + tokenQuant < maxSupply,
            "Would exceed max supply"
        );

        require(
            msg.value == price * tokenQuant,
            "Wrong amount of ETH sent - please check price!"
        );

        require(
            addressToNumberOfTokensMinted[msg.sender] + tokenQuant < maxMint,
            "Minting this many tokens takes you over your maximum amount!"
        );

        // Increasing minted count in dictionary
        addressToNumberOfTokensMinted[msg.sender] += tokenQuant;

        for (uint256 i; i < tokenQuant; i++) {
            _mint(msg.sender, tokenCounter);
            tokenCounter = tokenCounter + 1;
        }
    }

    //Request from Mecharcade to get a verified Chainlink random number to assign mechs to tokenIDs. Please check
    //the website FAQ for more details on how the reveal will happen and how we are using Chainlink's VRF for a 
    //provably fair distribution of the Mechs.
    function chainLinkGetSeed() public onlyOwner {
        require(
            LINK.balanceOf(address(this)) >= fee,
            "Not enough LINK - fill contract with faucet"
        );

        requestRandomness(keyHash, fee);
    }

    //Called by Chainlink with requested random number.
    function fulfillRandomness(bytes32 requestId, uint256 randomNumber)
        internal
        override
    {
        rngSeed = randomNumber;
        emit receivedChainlinkVRF(requestId, randomNumber);
    }

    //Set the MintingPhase which controls contract behavior.
    function setMintPhase(MintPhase phase, uint256 maxAmount) public onlyOwner {
        liveStatus = phase;
        maxMint = maxAmount;
    }

    //Set the base path on Pinata.
    function reveal(string memory base) public onlyOwner {
        require(
            mutableMetadata, // Check to make sure the collection hasn't been frozen before allowing the metadata to change
            "Metadata is frozen on the blockchain forever"
        );
        baseURI = base;
        emit executedReveal(tokenCounter - revealedMechs);
        revealedMechs = tokenCounter;
    }

    //Allows owner to manually set rngSeed from external source
    function setRNGSeed(uint256 _rngSeed) public onlyOwner {
        rngSeed = _rngSeed;
    }

    //Extract the signer address for the whitelist verification.
    function recoverSigner_94g(bytes memory _signature)
        internal
        view
        returns (address)
    {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        bytes32 senderhash = keccak256(abi.encodePacked(msg.sender));
        bytes32 finalhash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", senderhash)
        );

        return ecrecover(finalhash, v, r, s);
    }

    //Extract the components of the whitelist signature.
    function splitSignature(bytes memory sig)
        public
        pure
        returns (
            bytes32 r,
            bytes32 s,
            uint8 v
        )
    {
        require(sig.length == 65, "invalid signature length");
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
    }

    // Approving OpenSea Proxy to reduce gas costs for secondary sales 
    function isApprovedForAll(address _owner, address operator) public view override returns (bool) {
        OpenSeaProxyRegistry proxyRegistry = OpenSeaProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(_owner)) == operator) return true;
        return super.isApprovedForAll(_owner, operator);
    }
}
function splitSignature(bytes memory sig)
    public
    pure
    returns (
        bytes32 r,
        bytes32 s,
        uint8 v
    )
{
    require(sig.length == 65, "invalid signature length");
    assembly {
        r := mload(add(sig, 32))
        s := mload(add(sig, 64))
        v := byte(0, mload(add(sig, 96)))
    }
}

//Extract the components of the whitelist signature.