contract PumpkinCrew is ERC721Enumerable, ReentrancyGuard, Rescue {

  /// @notice Available NFT supply
  uint256 public immutable AVAILABLE_SUPPLY;
  /// @notice Cost to mint each NFT (in wei)
  uint256 public immutable MINT_COST;
  /// @notice Maximum mints per address
  uint256 public immutable MAX_PER_ADDRESS;
  /// @notice Start time for minting
  uint256 public immutable MINTING_START_TIME;

  /// @notice Earliest reveal time
  uint256 public immutable REVEAL_TIME;
  /// @notice hashed final metadata: keccak256(abi.encodePacked(_baseTokenURI)
  bytes32 public immutable METADATA_HASH;
  /// @notice Base URI used when returning token metadata
  string public baseTokenURI;
  /// @notice Indicator if final metadata is revealed
  bool public revealed = false;
  /// @notice Indicator if to include token id in token uri before the reveal
  bool public includeTokenId = false;

  /// @notice Address to number of mints
  mapping(address => uint256) public mintsPerAddress;
  /// @notice Number of NFTs minted
  uint256 public totalMinted = 0;

  constructor(
    string memory _NFT_NAME,
    string memory _NFT_SYMBOL,
    uint256 _MINTING_START_TIME,
    uint256 _MINT_COST,
    uint256 _AVAILABLE_SUPPLY,
    uint256 _MAX_PER_ADDRESS,
    uint256 _METADATA_REVEAL_TIME,
    bytes32 _METADATA_HASH,
    string memory _baseTokenURI
  )
  ERC721(_NFT_NAME, _NFT_SYMBOL)
  {
    MINTING_START_TIME = _MINTING_START_TIME;
    REVEAL_TIME = _METADATA_REVEAL_TIME;
    METADATA_HASH = _METADATA_HASH;
    MINT_COST = _MINT_COST;
    AVAILABLE_SUPPLY = _AVAILABLE_SUPPLY;
    MAX_PER_ADDRESS = _MAX_PER_ADDRESS;
    baseTokenURI = _baseTokenURI;
  }

  function mintNft(uint256 count) external payable nonReentrant {
    _mintNft(msg.sender, count);
  }

  function mintNftOnBehalf(address beneficiary, uint256 count) external payable nonReentrant {
    require(beneficiary != address(0), "address");

    _mintNft(beneficiary, count);
  }

  function mintNftAdmin(address beneficiary, uint256 count) external nonReentrant onlyOwner {
    require(beneficiary != address(0), "address");

    _mintNftAdmin(beneficiary, count);
  }

  /**
  * @dev Admin can change base URI as many times as needed before the reveal.
  */
  function changePreRevealBaseTokenURI(string calldata _baseTokenURI) external onlyOwner {
    require(!revealed, "revealed");

    baseTokenURI = _baseTokenURI;
  }

  /**
  * @dev Reveal final metadata. Should match the stored hash.
  */
  function revealMetadata(string calldata _baseTokenURI) external onlyOwner {
    require(totalMinted == AVAILABLE_SUPPLY || block.timestamp >= REVEAL_TIME, "not yet");
    require(keccak256(abi.encodePacked(_baseTokenURI)) == METADATA_HASH, "hash");
    require(!revealed, "revealed");

    revealed = true;
    baseTokenURI = _baseTokenURI;
  }

  /**
  * @dev Admin can change if token id should be included in token URI before the reveal
  */
  function setIncludeTokenId(bool _include) external onlyOwner {
    includeTokenId = _include;
  }

  /**
  * @dev Burns `tokenId`. See {ERC721-_burn}.
  * Requirements:
  * - The caller must own `tokenId` or be an approved operator.
  */
  function burn(uint256 tokenId) external {
    require(_isApprovedOrOwner(_msgSender(), tokenId), "not owner nor approved");
    _burn(tokenId);
  }

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

  function tokenURI(uint256 tokenId) public view override returns (string memory) {
    if (revealed || includeTokenId) {
      return super.tokenURI(tokenId);
    }
    require(_exists(tokenId), "nonexistent");
    return baseTokenURI;
  }

  function _mintNft(address _beneficiary, uint256 _count) internal {
    require(block.timestamp >= MINTING_START_TIME, "start");
    require(!revealed, "revealed");
    require(_count > 0, "count");
    require(msg.value == _count * MINT_COST, "payment");

    uint256 addressMints = mintsPerAddress[_beneficiary] + _count;
    require(addressMints <= MAX_PER_ADDRESS, "max");

    uint256 nextTokenId = totalMinted;
    uint256 totalMints = nextTokenId + _count;
    require(totalMints <= AVAILABLE_SUPPLY, "supply");

    for (uint256 i = 0; i < _count; i++) {
      _safeMint(_beneficiary, nextTokenId);
      nextTokenId++;
    }
    mintsPerAddress[_beneficiary] = addressMints;
    totalMinted = totalMints;
  }

  function _mintNftAdmin(address _beneficiary, uint256 _count) internal {
    require(revealed, "revealed");
    require(_count > 0, "count");

    uint256 nextTokenId = totalMinted;
    uint256 totalMints = nextTokenId + _count;
    require(totalMints <= AVAILABLE_SUPPLY, "supply");

    for (uint256 i = 0; i < _count; i++) {
      _safeMint(_beneficiary, nextTokenId);
      nextTokenId++;
    }

    mintsPerAddress[owner()] += _count;
    totalMinted = totalMints;
  }
}
function revealMetadata(string calldata _baseTokenURI) external onlyOwner {
  require(totalMinted == AVAILABLE_SUPPLY || block.timestamp >= REVEAL_TIME, "not yet");
  require(keccak256(abi.encodePacked(_baseTokenURI)) == METADATA_HASH, "hash");
  require(!revealed, "revealed");

  revealed = true;
  baseTokenURI = _baseTokenURI;
}

/**
 * @dev Reveal final metadata. Should match the stored hash.
 */