contract ToyTransfers is ToyOwnership {
    //-------------------------------------------------------------------------
    /// @dev Transfer emits when ownership of a TOY Token changes by any
    ///  mechanism. This event emits when TOY Tokens are created ('from' == 0).
    ///  At the time of any transfer, the approved address for that TOY Token
    ///  (if any) is reset to address(0).
    //-------------------------------------------------------------------------
    event Transfer(
        address indexed _from, 
        address indexed _to, 
        uint256 indexed _tokenId
    );

    //-------------------------------------------------------------------------
    /// @dev Approval emits when the approved address for a TOY Token is
    ///  changed or reaffirmed. The zero address indicates there is no approved
    ///  address. When a Transfer event emits, this also indicates that the
    ///  approved address for that TOY Token (if any) is reset to none.
    //-------------------------------------------------------------------------
    event Approval(
        address indexed _owner, 
        address indexed _approved, 
        uint256 indexed _tokenId
    );

    //-------------------------------------------------------------------------
    /// @dev This emits when an operator is enabled or disabled for an owner.
    ///  The operator can manage all TOY Tokens of the owner.
    //-------------------------------------------------------------------------
    event ApprovalForAll(
        address indexed _owner, 
        address indexed _operator, 
        bool _approved
    );

    // Mapping from token ID to approved address
    mapping (uint => address) idToApprovedAddress;
    // Mapping from owner to operator approvals
    mapping (address => mapping (address => bool)) operatorApprovals;

    //-------------------------------------------------------------------------
    /// @dev Throws if called by any account other than token owner, approved
    ///  address, or authorized operator.
    //-------------------------------------------------------------------------
    modifier canOperate(uint _uid) {
        // sender must be owner of TOY Token #uid, or sender must be the
        //  approved address of TOY Token #uid, or an authorized operator for
        //  TOY Token owner
        require (
            msg.sender == toyArray[uidToToyIndex[_uid]].owner ||
            msg.sender == idToApprovedAddress[_uid] ||
            operatorApprovals[toyArray[uidToToyIndex[_uid]].owner][msg.sender],
            "Not authorized to operate for this TOY Token"
        );
        _;
    }

    //-------------------------------------------------------------------------
    /// @notice Change or reaffirm the approved address for TOY Token #`_tokenId`.
    /// @dev The zero address indicates there is no approved address.
    ///  Throws unless `msg.sender` is the current NFT owner, or an authorized
    ///  operator of the current owner.
    /// @param _approved The new approved TOY Token controller
    /// @param _tokenId The TOY Token to approve
    //-------------------------------------------------------------------------
    function approve(address _approved, uint256 _tokenId) external payable {
        address owner = ownerOf(_tokenId);
        // msg.sender must be the current NFT owner, or an authorized operator
        //  of the current owner.
        require (
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "Not authorized to approve for this TOY Token"
        );
        idToApprovedAddress[_tokenId] = _approved;
        emit Approval(owner, _approved, _tokenId);
    }
    
    //-------------------------------------------------------------------------
    /// @notice Get the approved address for a single NFT
    /// @dev Throws if `_tokenId` is not a valid NFT.
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if
    ///  there is none
    //-------------------------------------------------------------------------
    function getApproved(
        uint256 _tokenId
    ) external view mustExist(_tokenId) returns (address) {
        return idToApprovedAddress[_tokenId];
    }
    
    //-------------------------------------------------------------------------
    /// @notice Enable or disable approval for a third party ("operator") to
    ///  manage all of sender's TOY Tokens
    /// @dev Emits the ApprovalForAll event. The contract MUST allow multiple
    ///  operators per owner.
    /// @param _operator Address to add to the set of authorized operators
    /// @param _approved True if the operator is approved, false to revoke
    ///  approval
    //-------------------------------------------------------------------------
    function setApprovalForAll(address _operator, bool _approved) external {
        require(_operator != msg.sender, "Operator cannot be sender");
        operatorApprovals[msg.sender][_operator] = _approved;
        emit ApprovalForAll(msg.sender, _operator, _approved);
    }
    
    //-------------------------------------------------------------------------
    /// @notice Get whether '_operator' is approved to manage all of '_owner's
    ///  TOY Tokens
    /// @param _owner TOY Token Owner.
    /// @param _operator Address to check for approval.
    /// @return True if '_operator' is approved to manage all of '_owner's' TOY
    ///  Tokens.
    //-------------------------------------------------------------------------
    function isApprovedForAll(
        address _owner, 
        address _operator
    ) public view returns (bool) {
        return operatorApprovals[_owner][_operator];
    }

    
    //-------------------------------------------------------------------------
    /// @notice Transfers ownership of TOY Token #`_tokenId` from `_from` to 
    ///  `_to`
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. When transfer is complete, checks if
    ///  `_to` is a smart contract (code size > 0). If so, it calls
    ///  `onERC721Received` on `_to` and throws if the return value is not
    ///  `0x150b7a02`. If TOY Token is linked to an external NFT, this function
    ///  calls TransferFrom from the external address. Throws if this contract
    ///  is not an approved operator for the external NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    //-------------------------------------------------------------------------
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) 
        external 
        payable 
        mustExist(_tokenId) 
        canOperate(_tokenId) 
        notZero(uint(_to)) 
    {
        address owner = ownerOf(_tokenId);
        // _from address must be current owner of the TOY Token
        require (
            _from == owner, 
            "TOY Token not owned by '_from'"
        );
               
        // if TOY Token has a linked external NFT, call TransferFrom on the 
        //  external NFT contract
        ExternalNft memory externalNft = uidToExternalNft[_tokenId];
        if (externalNft.nftContractAddress != 0) {
            // initialize external NFT contract
            ERC721 externalContract = ERC721(externalNft.nftContractAddress);
            // call TransferFrom
            externalContract.transferFrom(_from, _to, externalNft.nftId);
        }

        // clear approval
        idToApprovedAddress[_tokenId] = 0;
        // transfer ownership
        toyArray[uidToToyIndex[_tokenId]].owner = _to;

        emit Transfer(_from, _to, _tokenId);

        // check and call onERC721Received. Throws and rolls back the transfer
        //  if _to does not implement the expected interface
        uint size;
        assembly { size := extcodesize(_to) }
        if (size > 0) {
            bytes4 retval = TokenReceiverInterface(_to).onERC721Received(msg.sender, _from, _tokenId, "");
            require(
                retval == 0x150b7a02, 
                "Destination contract not equipped to receive TOY Tokens"
            );
        }
    }
    
    //-------------------------------------------------------------------------
    /// @notice Transfers ownership of TOY Token #`_tokenId` from `_from` to 
    ///  `_to`
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. If TOY Token is linked to an external
    ///  NFT, this function calls TransferFrom from the external address.
    ///  Throws if this contract is not an approved operator for the external
    ///  NFT. When transfer is complete, checks if `_to` is a smart contract
    ///  (code size > 0). If so, it calls `onERC721Received` on `_to` and
    ///  throws if the return value is not `0x150b7a02`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param _data Additional data with no pre-specified format
    //-------------------------------------------------------------------------
    function safeTransferFrom(
        address _from, 
        address _to, 
        uint256 _tokenId, 
        bytes _data
    ) 
        external 
        payable 
        mustExist(_tokenId) 
        canOperate(_tokenId) 
        notZero(uint(_to)) 
    {
        address owner = ownerOf(_tokenId);
        // _from address must be current owner of the TOY Token
        require (
            _from == owner, 
            "TOY Token not owned by '_from'"
        );
        
        // if TOY Token has a linked external NFT, call TransferFrom on the 
        //  external NFT contract
        ExternalNft memory externalNft = uidToExternalNft[_tokenId];
        if (externalNft.nftContractAddress != 0) {
            // initialize external NFT contract
            ERC721 externalContract = ERC721(externalNft.nftContractAddress);
            // call TransferFrom
            externalContract.transferFrom(_from, _to, externalNft.nftId);
        }

        // clear approval
        idToApprovedAddress[_tokenId] = 0;
        // transfer ownership
        toyArray[uidToToyIndex[_tokenId]].owner = _to;

        emit Transfer(_from, _to, _tokenId);

        // check and call onERC721Received. Throws and rolls back the transfer
        //  if _to does not implement the expected interface
        uint size;
        assembly { size := extcodesize(_to) }
        if (size > 0) {
            bytes4 retval = TokenReceiverInterface(_to).onERC721Received(msg.sender, _from, _tokenId, _data);
            require(
                retval == 0x150b7a02,
                "Destination contract not equipped to receive TOY Tokens"
            );
        }
    }

    //-------------------------------------------------------------------------
    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. If TOY Token is linked to an external
    ///  NFT, this function calls TransferFrom from the external address.
    ///  Throws if this contract is not an approved operator for the external
    ///  NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    //-------------------------------------------------------------------------
    function transferFrom(address _from, address _to, uint256 _tokenId)
        external 
        payable 
        mustExist(_tokenId) 
        canOperate(_tokenId) 
        notZero(uint(_to)) 
    {
        address owner = ownerOf(_tokenId);
        // _from address must be current owner of the TOY Token
        require (
            _from == owner && _from != 0, 
            "TOY Token not owned by '_from'"
        );
        
        // if TOY Token has a linked external NFT, call TransferFrom on the 
        //  external NFT contract
        ExternalNft memory externalNft = uidToExternalNft[_tokenId];
        if (externalNft.nftContractAddress != 0) {
            // initialize external NFT contract
            ERC721 externalContract = ERC721(externalNft.nftContractAddress);
            // call TransferFrom
            externalContract.transferFrom(_from, _to, externalNft.nftId);
        }

        // clear approval
        idToApprovedAddress[_tokenId] = 0;
        // transfer ownership
        toyArray[uidToToyIndex[_tokenId]].owner = _to;

        emit Transfer(_from, _to, _tokenId);
    }
}
//-----------------------------------------------------------------------------
function getApproved(
    uint256 _tokenId
) external view mustExist(_tokenId) returns (address) {
    return idToApprovedAddress[_tokenId];
}

//-------------------------------------------------------------------------