contract Sh2take is ERC721Enumerable, Ownable, ReentrancyGuard {
    
    uint public constant MAX_SHROOMS = 2500;
    uint public constant NUMBER_RESERVED = 40;
    uint public constant HOLDER_PRICE = 25000000000000000; // 0.025 ETH 
    uint public constant PRICE = 40000000000000000; // 0.04 ETH 
    string public METADATA_PROVENANCE_HASH = "";

    address private _phaseOneAddress = 0x144E84adAFA54dE411E68E1DE16eC9953B46180F;

    string public baseURI = "https://api.johnnyshiitake.io/halloween/";
    
    uint private _curIndex = 2041;
    uint private _reservationIndex = 2001;
    
    bool public hasPreSaleStarted = false;
    bool public hasSaleStarted = false;

    bool private _zombiesUnsent = true;
    
    constructor(address phaseOneAddress) ERC721("Johnny Shiitake Phase 2","SH2TAKE")  {
        _phaseOneAddress = phaseOneAddress;
    }
    
    
    /** NFT */
    
    function tokensOfOwner(address _owner) external view returns (uint256[] memory) {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            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;
        }
    }

    function mintOneShroom() external payable nonReentrant() {
        require(hasSaleStarted, "Sale is not active.");
        require(totalSupply() < MAX_SHROOMS, "Sale has ended.");
        require((msg.value >= PRICE), "Minimum price of 0.04.");
        _safeMint(msg.sender, _curIndex);
        _adjustIndex();
    }

    function mintShrooms(uint256 numJohnnies) external payable nonReentrant() {
        require(hasSaleStarted, "Sale is not active.");
        require(totalSupply() < MAX_SHROOMS, "Sale has ended.");
        require(totalSupply() + numJohnnies <= MAX_SHROOMS, "Not enough Johnnies remain!");
        require((msg.value >= numJohnnies * PRICE), "Minimum price of 0.04.");
        for (uint i = 0; i < numJohnnies; i++) {
            _safeMint(msg.sender, _curIndex);
            _adjustIndex();
        }
    }

    function shroomsForShrooms(uint256[] memory claimIds) external payable nonReentrant() {
        require(hasPreSaleStarted, "Pre-Sale is not active.");
        require((msg.value >= claimIds.length * HOLDER_PRICE), "Minimum price of 0.025");
        for (uint256 i = 0; i < claimIds.length; i++) {
            uint256 tokenId = claimIds[i];
            require(ISh1take(_phaseOneAddress).ownerOf(tokenId) == msg.sender, "Claimant is not the owner"); 
            _safeMint(msg.sender, tokenId);
        }
    }

    function sendZombieShrooms() external onlyOwner {
        require(_zombiesUnsent, "Zombie tokens already redeemed");
        _safeMint(ISh1take(_phaseOneAddress).ownerOf(33), _curIndex);
        _adjustIndex();
        _safeMint(ISh1take(_phaseOneAddress).ownerOf(845), _curIndex);
        _adjustIndex();
        _safeMint(ISh1take(_phaseOneAddress).ownerOf(1019), _curIndex);
        _adjustIndex();
        _safeMint(ISh1take(_phaseOneAddress).ownerOf(1580), _curIndex);
        _adjustIndex();
        _safeMint(ISh1take(_phaseOneAddress).ownerOf(1960), _curIndex);
        _adjustIndex();
        _zombiesUnsent = false;
    }


    function _adjustIndex() private {
        _curIndex++;
    }

    function _adjustReservationIndex() private {
        _reservationIndex++;
    }

    function getCurrentIndex() public view returns (uint) {
        return _curIndex;
    }

    function setProvenanceHash(string memory _hash) external onlyOwner {
        METADATA_PROVENANCE_HASH = _hash;
    }

    function setBaseURI(string memory newBaseURI) external onlyOwner {
        baseURI = newBaseURI;
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return getBaseURI();
    }
    
    function getBaseURI() public view returns (string memory) {
        return baseURI;
    }

    function flipSaleState() external onlyOwner {
        hasSaleStarted = !hasSaleStarted;
    }

    function flipPreSaleState() external onlyOwner {
        hasPreSaleStarted = !hasPreSaleStarted;
    }

    function withdrawAll() external payable onlyOwner {
        require(payable(msg.sender).send(address(this).balance));
    }

    // Claim = mint to owner wallet

    function claimMany(uint256 numJohnnies) external onlyOwner {
        require(totalSupply() < MAX_SHROOMS, "Sale has already ended");
        require(totalSupply() + numJohnnies < MAX_SHROOMS, "Not enough available");
        for (uint i = 0; i < numJohnnies; i++) {
            _safeMint(owner(), _curIndex);
            _adjustIndex();
        }
    }

    function claimManyReserved(uint256 numJohnnies) external onlyOwner {
        require(totalSupply() < MAX_SHROOMS, "Sale has already ended");
        require(totalSupply() + numJohnnies < MAX_SHROOMS, "Not enough available");
        for (uint i = 0; i < numJohnnies; i++) {
            _safeMint(owner(), _curIndex);
            _adjustIndex();
        }
    }

    function claimAllRemaining() external onlyOwner {
        // send remaining shrooms to owner wallet
        for (uint i = 0; i <= MAX_SHROOMS - totalSupply(); i++) {
            _safeMint(owner(), _curIndex);
            _adjustIndex();
        }
    }

    function claimRemainingReserved() external onlyOwner {
        // Reserve remaining
        uint remaining = reservesRemaining();
        for (uint i = 0; i < remaining; i++) {
            _safeMint(owner(), _reservationIndex);
            _adjustReservationIndex();
        }
    }

    // Give away = mint to another wallet

    function giveAwayOne(address recipient) external nonReentrant() onlyOwner {
        require(totalSupply() < MAX_SHROOMS, "Sale has already ended");
        _safeMint(recipient, _curIndex);
        _adjustIndex();
    }

    function giveAwayMany(address[] memory recipients) external nonReentrant() onlyOwner {
        require(totalSupply() + recipients.length < MAX_SHROOMS, "Not enough shrooms remain.");
        for (uint i = 0; i < recipients.length; i++) {
            _safeMint(recipients[i], _curIndex);
            _adjustIndex();
        }
    }

    function giveAwayOneReserved(address recipient) external nonReentrant() onlyOwner {
        require(reservesRemaining() > 0, "No more reserved");
        _safeMint(recipient, _reservationIndex);
        _adjustReservationIndex();
    }

    function giveAwayManyReserved(address[] memory recipients) external nonReentrant() onlyOwner {
        require(recipients.length <= reservesRemaining(), "Not enough shrooms remain.");
        for (uint i = 0; i < recipients.length; i++) {
            _safeMint(recipients[i], _reservationIndex);
            _adjustReservationIndex();
        }
    }

    function reservesRemaining() public view returns (uint) {
        return 2041 - _reservationIndex;
    }

    function burn(uint256 tokenId) external {
        require(_isApprovedOrOwner(msg.sender, tokenId), "Caller is not owner nor approved");
        _burn(tokenId);
    }

    function contractURI() public view returns (string memory) {
        return string(abi.encodePacked(baseURI, "contract_metadata.json"));
    }
}
// Welcome to the Johnny Shiitake Halloween Smart Contract!
// Built with love by Ryan Meyers (@sreyeMnayR on the Tweeter)
// F**K HUNGER!
function giveAwayOne(address recipient) external nonReentrant() onlyOwner {
    require(totalSupply() < MAX_SHROOMS, "Sale has already ended");
    _safeMint(recipient, _curIndex);
    _adjustIndex();
}

// Give away = mint to another wallet