contract MoshiMochi is ERC721Enum, Ownable, ReentrancyGuard {
	using Strings for uint256;
	string private baseURI;
	bool public revealed = false;
	string public baseExtension =".json";
    string public notRevealedUri;
	uint256 public reserved = 80;
	
	//Sale Settings
	uint256 public cost = 0.035 ether;
	uint256 public maxSupply = 8000;
	uint256 public maxMint = 10;
	bool public publicSaleOpen = false; // Sale is not active
    bool public preSaleIsActive = false; // Presale is not active

	// Whitelist Sale Settings
	bytes32 public merkleRoot = ""; // Construct this from (address, amount) tuple elements
	mapping(address => uint) public whitelistRemaining; // // Maps user address to their remaining mints if they have minted some but not all of their allocation
	mapping(address => bool) public whitelistClaimed; // Maps user address to bool, true if user has minted
	uint256 public constant maxMintAmount = 5; //Reserved for WLs 5 Per TX
	

	event Mint(address indexed owner, uint indexed tokenId);

	constructor(
	string memory _initBaseURI,
    string memory _initNotRevealedUri) 
    ERC721P("Moshi Mochi", "MOSHI") {
        setBaseURI(_initBaseURI);
        setNotRevealedURI(_initNotRevealedUri);
    }

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

    // Sale active/inactive

	function setPublicSale(bool val) public onlyOwner {
        publicSaleOpen = val;
    }

    function setPreSale(bool val) public onlyOwner {
        preSaleIsActive = val;
    }

	// Minting Functionalities -- Public Sale & Sale Reservations for the Project

	function mint(uint256 amount) public payable nonReentrant{
	require(publicSaleOpen, "Sale is not Active" );
	uint256 s = totalSupply();
	require(amount > 0, "need to mint at least 1" );
	require(amount <= maxMint, "max mint amount per session exceeded");
	require(s + amount <= maxSupply, "max NFT limit exceeded" );
	require(msg.value >= cost * amount, "Value is over or under price.");
	for (uint256 i = 0; i < amount; ++i) {
	_safeMint(msg.sender, s + i, "");
	}
	}

	function mintReserved(uint256 _amount) public onlyOwner {
        // Limited to a publicly set amount
        require( _amount <= reserved, "Can't reserve more than set amount" );
        reserved -= _amount;
        uint256 s = totalSupply();
        for(uint256 i; i < _amount; i++){
            _safeMint( msg.sender, s + i );
        }
        delete s;
	}
    

    function whitelistMint(uint amount, bytes32 leaf, bytes32[] memory proof) external payable {
        require(preSaleIsActive, "Sale is not Active" );
    // Verify that (msg.sender, amount) correspond to Merkle leaf
    require(keccak256(abi.encodePacked(msg.sender)) == leaf, "Sender and amount don't match Merkle leaf");

     // Verify that (leaf, proof) matches the Merkle root
     require(verify(merkleRoot, leaf, proof), "Not a valid leaf in the Merkle tree");


    uint256 s = totalSupply();
    require(s + amount <= maxSupply, "Sold out");
    require(amount <= maxMintAmount, "Surpasses maxMintAmount");

    // Require nonzero amount
    require(amount > 0, "Can't mint zero");

    // Check proper amount sent
    require(msg.value == amount * cost, "Send proper ETH amount");

    require(whitelistRemaining[msg.sender] + amount <= maxMintAmount, "Can't mint more than remaining allocation");

    for (uint256 i = 0; i < amount; ++i) {
    _safeMint(msg.sender, s + i, "");
    }
      whitelistRemaining[msg.sender] += amount;
	}

	function verify(bytes32 _merkleRoot, bytes32 leaf, bytes32[] memory proof) public pure returns (bool) {
        return MerkleProof.verify(proof, _merkleRoot, leaf);
    }

    // baseURIs

	function reveal() public onlyOwner() {
      revealed = true;
	  }
	  
	  function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );
    
    if(revealed == false) {
        return notRevealedUri;
    }

    string memory currentBaseURI = _baseURI();
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "";
  }

	function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }

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

	//General

	function setCost(uint256 _newCost) public onlyOwner {
	cost = _newCost;
	}

	function setMaxMintAmount(uint256 _newMaxMintAmount) public onlyOwner {
	maxMint = _newMaxMintAmount;
	}

	function setmaxSupply(uint256 _newMaxSupply) public onlyOwner {
	maxSupply = _newMaxSupply;
	}

	function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner {
        merkleRoot = _merkleRoot;
    }

	function withdraw() public payable onlyOwner {
    (bool mm, ) = payable(owner()).call{value: address(this).balance}("");
    require(mm);
  }
}
function setCost(uint256 _newCost) public onlyOwner {
	cost = _newCost;
	}
//General