contract Buidlverse is ERC721, Ownable {
  using MerkleProof for bytes32[];
  using ECDSA for bytes32;
  using Strings for uint256;
  using Counters for Counters.Counter;

  Counters.Counter private idCounter;

  string public provenanceHash = "";

  string public baseURI;
  string public baseExtension = ".json";

  uint256 public maxSupply;
  uint256 public cost = 0;
  uint256 public maxMintAmount = 20;

  bool public paused = false;

  uint256 public whitelistIndex = 1; // if 0, no whitelist is active, if >0, use Merkle whitelist
  mapping (uint256 => mapping (address => bool)) public whitelistClaimed; // index -> user -> claimed or not
  bytes32 public whitelistMerkleRoot;

  constructor(
    string memory _name,
    string memory _symbol,
    string memory _newBaseURI,
    uint256 _maxSupply
  ) ERC721(_name, _symbol) {
    baseURI = _newBaseURI;
    maxSupply = _maxSupply;
  }

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

  /**
  * @dev internally mints new tokenIds to target. tokenIds are incremented and minted in sequential order.
  * @param _to address to mint new tokenIds to
  * @param _mintAmount amount of new tokenIds to mint
  */
  function _mintInternal(address _to, uint256 _mintAmount) internal {
    require(!paused, "the contract is paused");

    require(_mintAmount > 0, "need to mint at least 1");
    require(_mintAmount <= maxMintAmount, "cannot mint more than maxMintAmount per call");
    require(totalSupply() + _mintAmount <= maxSupply, "exceeded max token limit");
    require(msg.value >= cost * _mintAmount, "insufficient funds");

    for (uint256 i = 0; i < _mintAmount; i++) {
      idCounter.increment();
      _safeMint(_to, idCounter.current());
    }
  }

  // public
  /**
  * @notice get metadata URI for a given tokenId
  */
  function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );
    
    string memory currentBaseURI = _baseURI();
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "";
  }

  /**
  * @notice get base metadata URI for all tokenIds and the collection
  */
  function baseTokenURI() public view returns (string memory) {
    return _baseURI();
  }

  /**
  * @notice get the latest tokenId
  */
  function currentId() public view returns (uint) {
    return idCounter.current();
  }

  /**
  * @notice get the total number of minted tokenIds
  */
  function totalSupply() public view returns (uint256) {
    return idCounter.current();
  }

  /**
  * @notice get the tokenId at a given index in the collection
  */
  function tokenByIndex(uint256 _index) public view returns (uint256) {
    require(_index < totalSupply(), "Global index out of bounds");
    return _index + 1;
  }

  /**
  * @notice check if the provided proof proves that the sender is whitelisted in the current Merkle tree
  */
  function verifyMerkleProof(address _sender, bytes32[] memory _proof) public view returns (bool) {
    bytes32 leaf = keccak256(abi.encodePacked(_sender));
    return _proof.verify(whitelistMerkleRoot,leaf);
  }

  // external
  /**
  * @notice when minting is open to public, mint given amount of tokenIds to an address
  * @param _to address to mint tokenId to
  * @param _mintAmount amount of new tokenIds to mint
  */
  function mint(address _to, uint256 _mintAmount) external payable {
    require(whitelistIndex == 0, "mint is whitelist-only right now");

    _mintInternal(_to, _mintAmount);
  }
  
  /**
  * @notice when minting is whitelist-only, mint 1 tokenId to an address specified by the whitelisted sender
  * @param _to address to mint tokenId to
  * @param _proof the Merkle proof to prove the sender is whitelisted in the Merkle tree
  */
  function mintWithWhitelist(address _to, bytes32[] memory _proof) external payable {
    require(whitelistIndex > 0, "mint is not in whitelist mode right now");
    require(!whitelistClaimed[whitelistIndex][msg.sender], "whitelist spot already claimed");

    require(whitelistMerkleRoot != "", "whitelist merkleRoot missing");
    require(verifyMerkleProof(msg.sender, _proof), "failed to verify merkle proof");

    whitelistClaimed[whitelistIndex][msg.sender] = true;
    _mintInternal(_to, 1);
  }
  
  /**
  * @dev admin sets the cost to mint 1 tokenId
  */
  function setCost(uint256 _newCost) external onlyOwner {
    cost = _newCost;
  }

  /**
  * @dev admin sets the maximum number of tokenIds that can be minted in a single call to the mint function
  */
  function setMaxMintAmount(uint256 _newmaxMintAmount) external onlyOwner {
    maxMintAmount = _newmaxMintAmount;
  }

  /**
  * @dev admin sets base metadata URI for all tokenIds and the collection
  */
  function setBaseURI(string memory _newBaseURI) external onlyOwner {
    baseURI = _newBaseURI;
  }

  /**
  * @dev admin sets the file extension for metadata objects
  */
  function setBaseExtension(string memory _newBaseExtension) external onlyOwner {
    baseExtension = _newBaseExtension;
  }
  
  /**
  * @dev admin pauses or unpauses minting of new tokenIds
  */
  function pause(bool _paused) external onlyOwner {
    paused = _paused;
  }

  /**
  * @dev admin uploads a new whitelist or removes current whitelist
  * @param _newIndex new index of the whitelist
  * @param _newRoot new Merkle root of the whitelist
  */
  function setWhitelist(uint256 _newIndex, bytes32 _newRoot) external onlyOwner {
    whitelistIndex = _newIndex;
    whitelistMerkleRoot = _newRoot;
  }

  /**
  * @dev admin sets the provenance hash once it is generated or updated
  */
  function setProvenanceHash(string memory _newHash) external onlyOwner {
    provenanceHash = _newHash;
  }

  /**
  * @dev admin sets maximum number of tokenIds that can be minted in this collection
  */
  function setMaxSupply(uint256 _maxSupply) external onlyOwner {
    require(_maxSupply >= totalSupply(), "max supply cannot be lower than current supply");
    maxSupply = _maxSupply;
  }
   
  /**
  * @dev admin withdraws all Ether that is available in this contract to the given address
  */
  function withdraw(address _payee) external payable onlyOwner {
    // This is the latest recommended way to withdraw funds in Solidity 0.8
    // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
    (bool success, ) = payable(_payee).call{value: address(this).balance}("");
    require(success, "transfer failed");
  }
}
/**
 * @title Buidlverse ERC721 contract
 * @dev This contract inherits from ERC721, but does not have the ERC721Enumerable extension.
 * ERC721Enumerable was deemed not worthwhile for the added mint and transfer costs
 */
function setBaseExtension(string memory _newBaseExtension) external onlyOwner {
  baseExtension = _newBaseExtension;
}

/**
 * @dev admin sets the file extension for metadata objects
 */