contract TastyToastys is Ownable, ERC721A, ReentrancyGuard {
  using Strings for uint256;
  uint256 public immutable maxPerAddressDuringMint;
  uint256 public immutable amountForDevs;
    // Provenance
  string public TastyToastys_HASH = "";
  uint256 public publicMintStartBlock =
    0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
  uint256 public allowlistMintStartBlock =
    0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
  uint256 public allowOGlistMintStartBlock =
    0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
  uint256 public publicPrice = 20000000000000000;
  bytes32 public allowlistMerkleRoot;
  bytes32 public allowOGlistMerkleRoot;
  uint256 public maxAllowListMint = 2;
  uint256 public maxOGAllowListMint = 5;
  mapping(address => bool) public allowlistClaimed;
  mapping(address => bool) public OGallowlistClaimed;
  constructor(
    uint256 maxBatchSize_,
    uint256 collectionSize_,
    uint256 amountForDevs_
  ) ERC721A("TastyToastys", "TASTYTOASTYS", maxBatchSize_, collectionSize_) {
    maxPerAddressDuringMint = maxBatchSize_;
    amountForDevs = amountForDevs_;
    require(
      amountForDevs_ <= collectionSize_,
      "larger collection size needed"
    );
  }

  modifier callerIsUser() {
    require(tx.origin == msg.sender, "The caller is another contract");
    _;
  }

  function mintStarted() public view returns (bool) {
    return block.number >= publicMintStartBlock;
  }

  function allowlistMintStarted() public view returns (bool) {
    return block.number >= allowlistMintStartBlock;
  }

  function allowOGlistMintStarted() public view returns (bool) {
    return block.number >= allowOGlistMintStartBlock;
  }

  function allowlistMint(bytes32 [] calldata _merkleProof, uint256 quantity) public {
    //Basic data validation to ensure the wallet hasn't already claimed
    require(allowlistMintStarted(), 'Allowlist not started');
    require(!allowlistClaimed[msg.sender], "Address has already claimed.");
    require(quantity <= maxAllowListMint, "Cannot claim this many");
    
    //Verify the provided _merkleProof, given to us through the API call on the website.
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
    require(MerkleProof.verify(_merkleProof, allowlistMerkleRoot, leaf), "Invalid proof.");

    //Mark address as having claimed their token.
    allowlistClaimed[msg.sender] = true;

    // Mint the reserved token here
    _safeMint(msg.sender, quantity); 
    }

  function OGallowlistMint(bytes32 [] calldata _merkleProof, uint256 quantity) public {
    //Basic data validation to ensure the wallet hasn't already claimed
    require(allowOGlistMintStarted(), 'OGAllowlist not started');
    require(!OGallowlistClaimed[msg.sender], "Address has already claimed.");
    require(quantity <= maxOGAllowListMint, "Cannot claim this many");
    
    //Verify the provided _merkleProof, given to us through the API call on the website.
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
    require(MerkleProof.verify(_merkleProof, allowOGlistMerkleRoot, leaf), "Invalid proof.");

    //Mark address as having claimed their token.
    OGallowlistClaimed[msg.sender] = true;

    // Mint the reserved token here
    _safeMint(msg.sender, quantity); 
    }

  function mint(uint256 quantity) public payable nonReentrant {
    require(mintStarted(), 'Mint not started');
    publicSaleMint(quantity);
  }

  function publicSaleMint(uint256 quantity)
    private
    callerIsUser
  {
    require(totalSupply() + quantity <= collectionSize, "reached max supply");
    require(msg.value >= (publicPrice * quantity), "Need to send more ETH.");
    require(msg.value <= (publicPrice * quantity), "You have tried to send too much ETH");
    _safeMint(msg.sender, quantity);
  }


  // To replace OG Tokens
  function devMint(uint256 quantity) external onlyOwner {
    require(
      totalSupply() + quantity <= amountForDevs,
      "too many already minted before dev mint"
    );
    require(
      quantity % maxBatchSize == 0,
      "can only mint a multiple of the maxBatchSize"
    );
    uint256 numChunks = quantity / maxBatchSize;
    for (uint256 i = 0; i < numChunks; i++) {
      _safeMint(msg.sender, maxBatchSize);
    }
  }

  // // metadata URI
  string private _baseTokenURI;

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

  function setBaseURI(string calldata baseURI) external onlyOwner nonReentrant{
    _baseTokenURI = baseURI;
  }

  function setMerkleRoot(bytes32 newMerkleRoot) public onlyOwner nonReentrant{
    allowlistMerkleRoot = newMerkleRoot; 
  }

  function setOGMerkleRoot(bytes32 newOGMerkleRoot) public onlyOwner nonReentrant{
    allowOGlistMerkleRoot = newOGMerkleRoot; 
  }

  function setPublicMintStartBlock(uint256 _newPublicMintStartBlock)
    external
    onlyOwner
  {
    publicMintStartBlock = _newPublicMintStartBlock;
  }

  function setAllowListMintStartBlock(uint256 _newAllowlistMintStartBlock)
    external
    onlyOwner
    nonReentrant
  {
    allowlistMintStartBlock = _newAllowlistMintStartBlock;
  }

  function setAllowOGListMintStartBlock(uint256 _newAllowOGlistMintStartBlock)
    external
    onlyOwner
    nonReentrant
  {
    allowOGlistMintStartBlock = _newAllowOGlistMintStartBlock;
  }

  function setPublicPrice(uint64 _newPublicPrice)
    external
    onlyOwner
    nonReentrant
  {
    publicPrice = _newPublicPrice;
  }

  function setMaxAllowListMint(uint256 _newMaxAllowListMint)
    external
    onlyOwner
    nonReentrant
  {
    maxAllowListMint = _newMaxAllowListMint;
  }

  function setMaxOGAllowListMint(uint256 _newMaxOGAllowListMint)
    external
    onlyOwner
    nonReentrant
  {
    maxOGAllowListMint = _newMaxOGAllowListMint;
  }

  function withdrawMoney() external onlyOwner nonReentrant {
    (bool success, ) = msg.sender.call{value: address(this).balance}("");
    require(success, "Transfer failed.");
  }

  function setOwnersExplicit(uint256 quantity) external onlyOwner nonReentrant {
    _setOwnersExplicit(quantity);
  }

  function numberMinted(address owner) public view returns (uint256) {
    return _numberMinted(owner);
  }

  function getOwnershipData(uint256 tokenId)
    external
    view
    returns (TokenOwnership memory)
  {
    return ownershipOf(tokenId);
  }


  function setProvenanceHash(string memory provenanceHash) external onlyOwner nonReentrant {
    TastyToastys_HASH = provenanceHash;
  }

}
function devMint(uint256 quantity) external onlyOwner {
  require(
    totalSupply() + quantity <= amountForDevs,
    "too many already minted before dev mint"
  );
  require(
    quantity % maxBatchSize == 0,
    "can only mint a multiple of the maxBatchSize"
  );
  uint256 numChunks = quantity / maxBatchSize;
  for (uint256 i = 0; i < numChunks; i++) {
    _safeMint(msg.sender, maxBatchSize);
  }
}

// To replace OG Tokens