contract MIRA is Ownable, ERC721URIStorage, ReentrancyGuard {
    // Counter for token IDs
    uint256 internal tokenIdCounter_;
    // Token ID => Token Spender => Approval Status
    mapping(uint256 => mapping(address => bool)) internal ownerApprovals_;

    constructor(
        string memory _name, 
        string memory _symbol
    ) 
        Ownable()
        ERC721(_name, _symbol) 
        ERC721URIStorage()
    {

    }

    /**
     * @notice  This function can only be called by the owner. This function
     *          will revert if the `_to` address is a contract that does not
     *          implement `ERC721Receiver`.
     * @param   _tokenURI Metadata for newly minted token. 
     * @param   _to Address to receive the newly minted token. 
     */
    function mint(
        string memory _tokenURI,
        address _to
    ) external onlyOwner() nonReentrant() {
        // Incrementing counter
        tokenIdCounter_ += 1;

        _safeMint(_to, tokenIdCounter_);
        _setTokenURI(tokenIdCounter_, _tokenURI);
    }

    /**
     * @notice  Allows only the contract owner to update the URI of a specific 
     *          token.
     * @param   _tokenId The token to update.
     * @param   _newTokenURI The new URI for the specified token.
     */
    function updateTokenURI(
        uint256 _tokenId, 
        string memory _newTokenURI
    ) external onlyOwner() {
        _setTokenURI(_tokenId, _newTokenURI);
    } 

    /**
     * @notice  Allows only the owner of the contract to burn a specific token.
     * @param   _tokenId The specific token to be burnt.
     */
    function burn(uint256 _tokenId) external onlyOwner() {
        _burn(_tokenId);
    }

    /**
     * @notice  Allows only the contract owner to approve the transfer of a 
     *          token.
     * @param   _from The address that the token will be moved out of.
     * @param   _to The address that the token will be moved to.
     * @param   _tokenId The identifier of the token being moved.
     */
    function approveTransfer(
        address _from, 
        address _to, 
        uint256 _tokenId,
        bool _approved
    ) external onlyOwner() nonReentrant() {
        require(
            ownerOf(_tokenId) == _from,
            "From is not token owner"
        );
        ownerApprovals_[_tokenId][_to] = _approved;
    }

    /**
     * @notice  This internal function is called before any token moving 
     *          operation. This will prevent 
     * @param   _from The address that the token will be moved out of.
     * @param   _to The address that the token will be moved to.
     * @param   _tokenId The identifier of the token being moved.
     */
    function _beforeTokenTransfer(
        address _from, 
        address _to, 
        uint256 _tokenId
    ) internal override {
        // Checks that transfer has been approved.
        // Does not check on mint or burn.
        if(_from != address(0) && _to != address(0)) {
            require(
                ownerApprovals_[_tokenId][_to],
                "Transfer has been not approved"
            );
        }
    }
}
function _beforeTokenTransfer(
    address _from, 
    address _to, 
    uint256 _tokenId
) internal override {
    // Checks that transfer has been approved.
    // Does not check on mint or burn.
    if(_from != address(0) && _to != address(0)) {
        require(
            ownerApprovals_[_tokenId][_to],
            "Transfer has been not approved"
        );
    }
}

/**
 * @notice  This internal function is called before any token moving 
 *          operation. This will prevent 
 * @param   _from The address that the token will be moved out of.
 * @param   _to The address that the token will be moved to.
 * @param   _tokenId The identifier of the token being moved.
 */