contract FANSINFT is
    ERC1155,
    ERC1155MintBurn,
    ERC1155Metadata,
    Ownable,
    AdminRole,
    Pausable
{
    using Strings for string;
    using SafeMath for uint256;

    string public name;
    string public symbol;
    string private _contractURI;
    address private _vaultWallet;
    mapping(uint256 => uint256) private tokenSupply;
    mapping(uint256 => string) private tokenHash;

    struct TokenDetail {
        string name; //artist's name
        uint256 totalEdition; //limit for edition published
    }

    //  Mapping from token ID to TokenDetail
    mapping(uint256 => TokenDetail) internal tokenIdToTokenDetail;

    constructor(string memory uri_) public {
        name = "FANSI 1155";
        symbol = "FANSI";
        _setBaseMetadataURI(uri_);
        _vaultWallet = 0xebB27b23336f62eBdc44bd5faE46a8FcB068E0Ea;
    }

    /**
     * @dev mintProjectToken a new token and assigns _amount to an address
     * @param _tokenID specific Token ID
     * @param _amount Optional amount to supply the first owner
     * @param _tokenHash hash for token metadata
     * @param _name name for token artist
     * @param _totalEdition ttotalEdition for token
     */
    function mintProjectToken(
        uint256 _tokenID,
        uint256 _amount,
        string memory _tokenHash,
        string memory _name,
        uint256 _totalEdition
    ) public onlyAdmin whenNotPaused {
        require(_amount != 0, "FansiNFT: amount should be positive");
        require(bytes(_tokenHash).length > 0, "FansiNFT: uri should be set");
        tokenHash[_tokenID] = _tokenHash;

        emit URI(_tokenHash, _tokenID);

        tokenIdToTokenDetail[_tokenID] = TokenDetail({
            name: _name,
            totalEdition: _totalEdition
        });

        _mint(_vaultWallet, _tokenID, _amount, "");

        tokenSupply[_tokenID] = tokenSupply[_tokenID].add(_amount);
    }

    /**
     * @dev mintSingleToken a new token to an address
     * @param _to Address of the future owner of the token
     * @param _tokenID specific Token ID
     * @param _tokenHash hash for token metadata
     * @param _name name for token artist
     * @param _totalEdition ttotalEdition for token
     */
    function mintSingleToken(
        address _to,
        uint256 _tokenID,
        string memory _tokenHash,
        string memory _name,
        uint256 _totalEdition
    ) public onlyAdmin whenNotPaused {
        require(bytes(_tokenHash).length > 0, "FansiNFT: uri should be set");

        tokenHash[_tokenID] = _tokenHash;

        emit URI(_tokenHash, _tokenID);

        tokenIdToTokenDetail[_tokenID] = TokenDetail({
            name: _name,
            totalEdition: _totalEdition
        });

        _mint(_to, _tokenID, 1, "");

        operators[_to][_msgSender()] = true;
        emit ApprovalForAll(_to, _msgSender(), true);

        tokenSupply[_tokenID] = tokenSupply[_tokenID].add(1);
    }

    /**
     * @dev mintToken a new token to an address
     * @param _to Address of the future owner of the token
     * @param _tokenID specific Token ID
     * @param _tokenHash hash for token metadata
     * @param _name name for token artist
     * @param _totalEdition ttotalEdition for token
     */
    function mintToken(
        address _to,
        uint256 _tokenID,
        string memory _tokenHash,
        string memory _name,
        uint256 _totalEdition
    ) public onlyAdmin whenNotPaused {
        require(bytes(_tokenHash).length > 0, "FansiNFT: uri should be set");

        tokenHash[_tokenID] = _tokenHash;

        emit URI(_tokenHash, _tokenID);

        tokenIdToTokenDetail[_tokenID] = TokenDetail({
            name: _name,
            totalEdition: _totalEdition
        });

        _mint(_to, _tokenID, 1, "");
        tokenSupply[_tokenID] = tokenSupply[_tokenID].add(1);
    }

    /**
     * @notice burnToken _amount of tokens of a given token id
     * @param _from    The address to burn tokens from
     * @param _id      Token id to burn
     * @param _amount  The amount to be burned
     */
    function burnToken(
        address _from,
        uint256 _id,
        uint256 _amount
    ) public {
        require(
            _from == _msgSender() || isApprovedForAll(_from, _msgSender()),
            "FansiNFT: caller is not owner nor approved"
        );
        _burn(_from, _id, _amount);
        tokenSupply[_id] = tokenSupply[_id].sub(_amount);
    }

    function uri(uint256 _id) public view override returns (string memory) {
        return Strings.strConcat(baseMetadataURI, tokenHash[_id]);
    }

    /**
     * @dev Returns the total quantity for a token ID
     * @param _id uint256 ID of the token to query
     * @return amount of token in existence
     */
    function totalSupply(uint256 _id) public view returns (uint256) {
        return tokenSupply[_id];
    }

    /**
     * @dev Will update the base URL of token's URI
     * @param _newBaseMetadataURI New base URL of token's URI
     */
    function setBaseMetadataURI(string memory _newBaseMetadataURI)
        public
        onlyOwner
        whenNotPaused
    {
        _setBaseMetadataURI(_newBaseMetadataURI);
    }

    function setTokenHash(uint256 _id, string memory _tokenHash)
        public
        onlyAdmin
        whenNotPaused
    {
        _setTokenHash(_id, _tokenHash);
    }

    function _setTokenHash(uint256 _id, string memory _tokenHash) internal {
        tokenHash[_id] = _tokenHash;
    }

    function contractURI() public view returns (string memory) {
        return _contractURI;
    }

    function setContractURI(string memory contractURI_) public onlyOwner {
        _contractURI = contractURI_;
    }

    function vaultWallet() public view returns (address) {
        return _vaultWallet;
    }

    function setVaultWallet(address vaultWallet_) public onlyOwner {
        _vaultWallet = vaultWallet_;
    }

    function getTokenDetail(uint256 _tokenId)
        public
        view
        returns (string memory _name, uint256 _totalEdition)
    {
        return (
            tokenIdToTokenDetail[_tokenId].name,
            tokenIdToTokenDetail[_tokenId].totalEdition
        );
    }

    /**
     * @dev Pauses all token transfers.
     *
     * See {ERC1155Pausable} and {Pausable-_pause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function pause() public onlyOwner {
        _pause();
    }

    /**
     * @dev Unpauses all token transfers.
     *
     * See {ERC1155Pausable} and {Pausable-_unpause}.
     *
     * Requirements:
     *
     * - the caller must have the `PAUSER_ROLE`.
     */
    function unpause() public onlyOwner {
        _unpause();
    }
}
function totalSupply(uint256 _id) public view returns (uint256) {
    return tokenSupply[_id];
}

/**
 * @dev Returns the total quantity for a token ID
 * @param _id uint256 ID of the token to query
 * @return amount of token in existence
 */