contract Mushrooms is ERC721Enumerable, Ownable {
    using Strings for uint256;

    uint256 public constant MAX_NFT_SUPPLY = 1200;
    
    uint256 public constant reserveMushrooms = 200;

    uint256 public constant tokenPrice = 40000000000000000;

    uint256 public constant _reserveIndexStart = 1000;

    uint256 public constant maxPurchase = 10;

    address public constant devAddress = 0x0dd874F41cE844FcdaeBA33714B6197136D89B7F;

    // used for counting while minting the reserve tokens
    uint256 internal _reserveIndexCounter = 1000;

    // checks whether or not reserves have been taken already
    bool public reservesTaken = false;
    
    // SHA256 of concatenated hash string containing all the hashes of all 1200 tokens 
    string public FinalProofHash;

    // IPFS HASH containing all the token metadata in one json
    string public METADATA_IPFS_HASH;

    // ensures that we do not change the metadata multiple times
    bool public metadataRevealed = false;

    // whether the tokenURI link needs ".json" after the tokenId
    bool internal tokenURIDotJson = false;

    // Sale starts when the function "flipSaleIsActive()" is called
    bool public saleIsActive = false;

    // helper variable for artificial randomness
    uint256 internal _randomOffset = 0;
    
    // starts indexing tokenIds
    uint256 internal _currentTokenId = 0;

    constructor() ERC721("Magic Mushrooms", "SHROOM") {}

    function _getRandomId() internal returns (uint256) {
      uint256 id = uint256(keccak256(abi.encodePacked(block.timestamp + block.difficulty + _randomOffset))) % MAX_NFT_SUPPLY;

      if (totalSupply() < MAX_NFT_SUPPLY) {
        while(_exists(id))  {
        _randomOffset++;
        id = uint256(keccak256(abi.encodePacked(block.timestamp + block.difficulty + _randomOffset))) % MAX_NFT_SUPPLY;
        }
      }
      return id;
    }

    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        if (tokenURIDotJson == true) {
            return string(abi.encodePacked(baseURI(), _tokenId.toString(), ".json"));
        } else {
            return string(abi.encodePacked(baseURI(), _tokenId.toString()));
        }
    }

    function mintShrooms(uint256 numberOfTokens) public payable {
        require(saleIsActive, "Sale must be active to mint Mushrooms");
        require(numberOfTokens > 0 && numberOfTokens <= maxPurchase, "Can only mint 10 tokens at a time");
        require(_currentTokenId + numberOfTokens <= _reserveIndexStart, "Purchase would exceed max supply of public Mushrooms");
        require(msg.value >= tokenPrice * numberOfTokens, "Ether value sent is not correct");
        
        for (uint256 i = 0; i < numberOfTokens; i++) {
            uint256 randHash = _getRandomId();
            if (totalSupply() < MAX_NFT_SUPPLY) {
                _safeMint(msg.sender, randHash);
            }
        }
    }

    function tokensOfOwner(address _owner) external view returns(uint256[] memory ) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            // Return an empty array
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }

    // OWNER ONLY FUNCTIONS

    // reveals the metadata and prohibits further changes
    function revealMetadata(string memory finalHash, string memory metadataHash) public onlyOwner {
        require(metadataRevealed != true, "CryptoMunks: You've already revealed the metadata!");

        FinalProofHash = finalHash;
        METADATA_IPFS_HASH = metadataHash;
        metadataRevealed = true;
    }

    // withdraw all the Ether from the contract to the owner
    function withdrawAll() external onlyOwner {
        uint256 balance = address(this).balance;
        require(balance > 0, "not enough balance");
        payable(devAddress).transfer(balance*10/100);
        payable(msg.sender).transfer(address(this).balance);
    }

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

    function getReserves() public onlyOwner {
        require(reservesTaken != true, "Magic Mushrooms: Reserves already taken!");
        
        for (uint256 i = 0; i < 25; i++) {
            uint256 id = _reserveIndexCounter;
            if (totalSupply() < MAX_NFT_SUPPLY) {
                _safeMint(msg.sender, id);
                _reserveIndexCounter++;
            }
        }
        if (_reserveIndexCounter == 1200) {
            reservesTaken = true;
        }
    }
    
    // switches between using and not using the ".json" extension
    function switchDotJson() public onlyOwner {
        if (tokenURIDotJson == true) {
            tokenURIDotJson = false;
        } else {
            tokenURIDotJson = true;
        }
    }

    // begins the sale
    function flipSaleIsActive() public onlyOwner {
        saleIsActive = true;
    }
    
}
function flipSaleIsActive() public onlyOwner {
    saleIsActive = true;
}

// begins the sale