contract ERC721Minter is IERC2981, ERC721, Ownable {
    using SafeMath for uint256;
    using SafeMath for uint16;
    address private _mediaContract;

    struct RoyaltyInfo {
        address _royaltyRecipient;
        uint16 _royaltyPercentage;
    }

    mapping(uint256 => string) private _tokenURIs;

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        return _tokenURIs[tokenId];
    }

    mapping(uint256 => RoyaltyInfo) private _tokenRoyaltyInfo;

    modifier onlyMediaCaller() {
        require(
            msg.sender == _mediaContract,
            "ERC721Minter: Unauthorized Access!"
        );
        _;
    }

    // TODO: Set Name & Symbol
    constructor() ERC721("", "") Ownable() { }

    function supportsInterface(bytes4 interfaceId)
        public
        view
        virtual
        override(IERC165, ERC721)
        returns (bool)
    {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            interfaceId == type(IERC2981).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external
        view
        override
        returns (address receiver, uint256 royaltyAmount)
    {
        require(
            _exists(tokenId),
            "ERC721Minter: Royalty query for nonexistent token"
        );
        receiver = owner();
        royaltyAmount = salePrice
            .mul(_tokenRoyaltyInfo[tokenId]._royaltyPercentage)
            .div(10000);
    }

    /**
     * @notice This method is used to configure Media Contract address
     * @dev Should only be called by contract owner
     * @param mediaContract_ Address of the Media Contract
     */
    function configureMedia(address mediaContract_) external onlyOwner {
        require(
            _mediaContract == address(0),
            "ERC721Minter: Media Already Configured!"
        );
        require(
            mediaContract_ != address(0),
            "ERC721Minter: Invalid Media Contract Address!"
        );
        _mediaContract = mediaContract_;
    }

    /**
     * @notice This method is used to check if token with ID _tokenID exists
     * @param _tokenID ID of the token to check
     * @return true if token with ID _tokenID exists, false otherwise
     */
    function doesTokenExist(uint256 _tokenID)
        external
        view
        onlyMediaCaller
        returns (bool)
    {
        return _exists(_tokenID);
    }

    /**
     * @notice This method is used to mint a token
     * @param _tokenID ID of the token to mint
     * @param _owner Address of the token owner
     */
    function mintToken(
        uint256 _tokenID,
        address _owner,
        uint16 _royaltyPoints,
        string memory _uri
    ) external onlyMediaCaller {
        _safeMint(_owner, _tokenID);

        RoyaltyInfo memory _royaltyInfo = RoyaltyInfo(_owner, _royaltyPoints);
        _tokenRoyaltyInfo[_tokenID] = _royaltyInfo;

        _tokenURIs[_tokenID] = _uri;
    }

    function transferToken(
        address _tokenOwner,
        address _recipient,
        uint256 _tokenID
    ) external onlyMediaCaller {
        _safeTransfer(_tokenOwner, _recipient, _tokenID, "");
    }
}
/**
 * @title ERC721Minter
 * @dev This Contract is used to interact with ERC721 Contract
 */
function mintToken(
    uint256 _tokenID,
    address _owner,
    uint16 _royaltyPoints,
    string memory _uri
) external onlyMediaCaller {
    _safeMint(_owner, _tokenID);

    RoyaltyInfo memory _royaltyInfo = RoyaltyInfo(_owner, _royaltyPoints);
    _tokenRoyaltyInfo[_tokenID] = _royaltyInfo;

    _tokenURIs[_tokenID] = _uri;
}

/**
 * @notice This method is used to mint a token
 * @param _tokenID ID of the token to mint
 * @param _owner Address of the token owner
 */