contract GengNft is AccessControl, Ownable, ERC721("KryptoGangsters", "KGS") {
    using SafeMath for uint256;
    using Strings for uint256;

    // used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string public uri;
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    uint256 public currentTokenId = 1;
    // mapping for token URIs
    mapping (uint256 => string) private _tokenURIs;

    // events
    event NftMinted(uint256 id, address owner);

    constructor(string memory _uri) {
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(MINTER_ROLE, _msgSender());
        setBaseURI(_uri);
    }

    /**
     * @dev sets base URI for computing {tokenURI}
     */
    function setBaseURI(string memory _uri) public onlyOwner {
        uri = _uri;
    }

    /**
     * @dev sets `_tokenURI` as the tokenURI of `tokenId`.
     *
     * @param _tokenId token id
     * @param _tokenURI token uri
     */
    function _setTokenURI(uint256 _tokenId, string memory _tokenURI) internal virtual {
        require(_exists(_tokenId), "ERC721Metadata: URI set of nonexistent token");
        _tokenURIs[_tokenId] = _tokenURI;
    }

    /**
     * @dev returns token uri
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

        string memory _tokenURI = _tokenURIs[tokenId];

        // If there is no base URI, return the token URI.
        if (bytes(uri).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(uri, _tokenURI));
        }
        // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
        return string(abi.encodePacked(uri, tokenId.toString()));
    }


    /**
     * @notice mints an nft.
     *
     * @param _metadataURI must be Ipfs uri for nft metadata
     * @param _owner address to mint NFT to
     */
    function mint(
        address _owner,
        string memory _metadataURI
    ) public returns (uint256) {
        require(hasRole(MINTER_ROLE, _msgSender()), "Caller is not a minter");
        uint256 id = currentTokenId;
        currentTokenId = currentTokenId.add(1);

        _mint(_owner, id);
        _setTokenURI(id, _metadataURI);

        emit NftMinted(id, _owner);
        return id;
    }

    /**
     * @notice burn an NFT.
     *
     * @param _id the NFT id to burn.
     */
    function burn(uint256 _id) public {
        _burn(_id);
    }

    /**
     * @dev ERC721 and AccessControl include supportsInterface so we need to override both
     */
    function supportsInterface(bytes4 _interfaceId)
        public
        view
        virtual
        override(ERC721, AccessControl)
        returns (bool)
    {
        return super.supportsInterface(_interfaceId);
    }
}
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
    require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

    string memory _tokenURI = _tokenURIs[tokenId];

    // If there is no base URI, return the token URI.
    if (bytes(uri).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(uri, _tokenURI));
    }
    // If there is a baseURI but no tokenURI, concatenate the tokenID to the baseURI.
    return string(abi.encodePacked(uri, tokenId.toString()));
}

/**
 * @dev returns token uri
 */