contract KramaNFT is AccessControlEnumerable, ERC721Enumerable {
  using Strings for uint256;

  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  // one or more smart contracts allowed to call the mint function, eg. the Marketplace contract

  string private _baseTokenURI; // Base URI
  mapping(uint256 => string) private _tokenURIs; // Optional mapping for token URIs

  /**
   * @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` to the
   * account that deploys the contract.
   *
   * Token URIs will be autogenerated based on `baseURI` and their token IDs.
   * See {ERC721-tokenURI}.
   */
  constructor(
    string memory name,
    string memory symbol,
    string memory baseTokenURI // e.g. https://gateway.pinata.cloud/ipfs/
  ) ERC721(name, symbol) {
    _baseTokenURI = baseTokenURI;
    address _admin = 0x7D1b56991dfe80bc8cbf02781AB3e70e54eD6D55;

    _setupRole(DEFAULT_ADMIN_ROLE, _admin);
    _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
  }

  /**
   * Optional function to set the base URI
   */
  function setBaseURI(string memory baseURI_) external {
    require(
      hasRole(DEFAULT_ADMIN_ROLE, msg.sender),
      "KramaNFT: must have DEFAULT_ADMIN_ROLE to set base URI"
    );
    _baseTokenURI = baseURI_;
  }

  function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(_exists(tokenId), "KramaNFT: URI query for nonexistent token");

    string memory _tokenURI = _tokenURIs[tokenId];
    string memory base = _baseURI();

    // If there is no base URI, return the token URI.
    if (bytes(base).length == 0) {
      return _tokenURI;
    }
    // If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked).
    if (bytes(_tokenURI).length > 0) {
      return string(abi.encodePacked(base, _tokenURI));
    }

    return super.tokenURI(tokenId);
  }

  /**
   * @dev Creates a new token for `msg.sender`. Its token ID will be automatically
   * assigned (and available on the emitted {IERC721-Transfer} event), and the token
   * URI autogenerated based on the base URI passed at construction.
   * See {ERC721-_mint}.
   *
   * Requirements:
   *
   * - the caller must have the `MINTER_ROLE` role.
   */
  function mint(string memory _tokenURI, address _to) external {
    require(
      hasRole(MINTER_ROLE, msg.sender),
      "KramaNFT: Must have MINTER_ROLE to mint"
    ); // MINTER_ROLE is assigned to contracts such as the marketplace, NOT artists in this case.

    _mint(_to, totalSupply()); // using total supply as counter, since tokens cannot be burned we don't need a separate counter
    _setTokenURI(totalSupply() - 1, _tokenURI); // set URI after minting otherwise it throws because the token doesn't exist yet; the token id just minted will be equal to totalSupply() - 1.
  }

  /**
   * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
   *
   * Requirements:
   *
   * - `tokenId` must exist.
   */
  function _setTokenURI(uint256 tokenId, string memory _tokenURI)
    internal
    virtual
  {
    require(_exists(tokenId), "KramaNFT: URI set of nonexistent token");
    _tokenURIs[tokenId] = _tokenURI;
  }

  function isTokenExists(uint256 tokenId) external view returns (bool) {
    return _exists(tokenId);
  }

  /**
   * @dev overrides the base function which is empty by default
   */
  function _baseURI() internal view virtual override returns (string memory) {
    return _baseTokenURI;
  }

  function _beforeTokenTransfer(
    address from,
    address to,
    uint256 tokenId
  ) internal virtual override(ERC721Enumerable) {
    super._beforeTokenTransfer(from, to, tokenId);
  }

  /**
   * @dev See {IERC165-supportsInterface}.
   */
  function supportsInterface(bytes4 interfaceId)
    public
    view
    virtual
    override(AccessControlEnumerable, ERC721Enumerable)
    returns (bool)
  {
    return super.supportsInterface(interfaceId);
  }
}
function _baseURI() internal view virtual override returns (string memory) {
  return _baseTokenURI;
}

/**
 * @dev overrides the base function which is empty by default
 */