contract WizardNFT is ERC165Interface, IERC721, WizardConstants, Address {

    /// @notice Emitted when a wizard token is created.
    event WizardConjured(uint256 wizardId, uint8 affinity, uint256 innatePower);

    /// @notice Emitted when a Wizard's affinity is set. This only applies for
    ///         Exclusive Wizards who can have the ELEMENT_NOT_SET affinity,
    ///         and should only happen once for each Wizard.
    event WizardAffinityAssigned(uint256 wizardId, uint8 affinity);

    // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
    bytes4 internal constant _ERC721_RECEIVED = 0x150b7a02;

    /// @dev The base Wizard structure.
    /// Designed to fit in two words.
    struct Wizard {
        // NOTE: Changing the order or meaning of any of these fields requires an update
        //   to the _createWizard() function which assumes a specific order for these fields.
        uint8 affinity;
        uint88 innatePower;
        address owner;
        bytes32 metadata;
    }

    // Mapping from Wizard ID to Wizard struct
    mapping (uint256 => Wizard) public wizardsById;

    // Mapping from Wizard ID to address approved to control them
    mapping (uint256 => address) private wizardApprovals;

    // Mapping from owner address to number of owned Wizards
    mapping (address => uint256) internal ownedWizardsCount;

    // Mapping from owner to Wizard controllers
    mapping (address => mapping (address => bool)) private _operatorApprovals;

    /// @dev 0x80ac58cd ===
    ///    bytes4(keccak256('balanceOf(address)')) ^
    ///    bytes4(keccak256('ownerOf(uint256)')) ^
    ///    bytes4(keccak256('approve(address,uint256)')) ^
    ///    bytes4(keccak256('getApproved(uint256)')) ^
    ///    bytes4(keccak256('setApprovalForAll(address,bool)')) ^
    ///    bytes4(keccak256('isApprovedForAll(address,address)')) ^
    ///    bytes4(keccak256('transferFrom(address,address,uint256)')) ^
    ///    bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^
    ///    bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)'))
    bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;

    /// @notice Query if a contract implements an interface
    /// @param interfaceId The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///      uses less than 30,000 gas.
    function supportsInterface(bytes4 interfaceId) public view returns (bool) {
        return
            interfaceId == this.supportsInterface.selector || // ERC165
            interfaceId == _INTERFACE_ID_ERC721; // ERC721
    }

    /// @notice Gets the number of Wizards owned by the specified address.
    /// @param owner Address to query the balance of.
    /// @return uint256 representing the amount of Wizards owned by the address.
    function balanceOf(address owner) public view returns (uint256) {
        require(owner != address(0), "ERC721: balance query for the zero address");
        return ownedWizardsCount[owner];
    }

    /// @notice Gets the owner of the specified Wizard
    /// @param wizardId ID of the Wizard to query the owner of
    /// @return address currently marked as the owner of the given Wizard
    function ownerOf(uint256 wizardId) public view returns (address) {
        address owner = wizardsById[wizardId].owner;
        require(owner != address(0), "ERC721: owner query for nonexistent token");
        return owner;
    }

    /// @notice Approves another address to transfer the given Wizard
    /// The zero address indicates there is no approved address.
    /// There can only be one approved address per Wizard at a given time.
    /// Can only be called by the Wizard owner or an approved operator.
    /// @param to address to be approved for the given Wizard
    /// @param wizardId ID of the Wizard to be approved
    function approve(address to, uint256 wizardId) public {
        address owner = ownerOf(wizardId);
        require(to != owner, "ERC721: approval to current owner");
        require(
            msg.sender == owner || isApprovedForAll(owner, msg.sender),
            "ERC721: approve caller is not owner nor approved for all"
        );

        wizardApprovals[wizardId] = to;
        emit Approval(owner, to, wizardId);
    }

    /// @notice Gets the approved address for a Wizard, or zero if no address set
    /// Reverts if the Wizard does not exist.
    /// @param wizardId ID of the Wizard to query the approval of
    /// @return address currently approved for the given Wizard
    function getApproved(uint256 wizardId) public view returns (address) {
        require(_exists(wizardId), "ERC721: approved query for nonexistent token");
        return wizardApprovals[wizardId];
    }

    /// @notice Sets or unsets the approval of a given operator.
    /// An operator is allowed to transfer all Wizards of the sender on their behalf.
    /// @param to operator address to set the approval
    /// @param approved representing the status of the approval to be set
    function setApprovalForAll(address to, bool approved) public {
        require(to != msg.sender, "ERC721: approve to caller");
        _operatorApprovals[msg.sender][to] = approved;
        emit ApprovalForAll(msg.sender, to, approved);
    }

    /// @notice Tells whether an operator is approved by a given owner.
    /// @param owner owner address which you want to query the approval of
    /// @param operator operator address which you want to query the approval of
    /// @return bool whether the given operator is approved by the given owner
    function isApprovedForAll(address owner, address operator) public view returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /// @notice Transfers the ownership of a given Wizard to another address.
    /// Usage of this method is discouraged, use `safeTransferFrom` whenever possible.
    /// Requires the msg.sender to be the owner, approved, or operator.
    /// @param from current owner of the Wizard.
    /// @param to address to receive the ownership of the given Wizard.
    /// @param wizardId ID of the Wizard to be transferred.
    function transferFrom(address from, address to, uint256 wizardId) public {
        require(_isApprovedOrOwner(msg.sender, wizardId), "ERC721: transfer caller is not owner nor approved");

        _transferFrom(from, to, wizardId);
    }

    /// @notice Safely transfers the ownership of a given Wizard to another address
    /// If the target address is a contract, it must implement `onERC721Received`,
    /// which is called upon a safe transfer, and return the magic value
    /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
    /// the transfer is reverted.
    /// Requires the msg.sender to be the owner, approved, or operator.
    /// @param from current owner of the Wizard.
    /// @param to address to receive the ownership of the given Wizard.
    /// @param wizardId ID of the Wizard to be transferred.
    function safeTransferFrom(address from, address to, uint256 wizardId) public {
        safeTransferFrom(from, to, wizardId, "");
    }

    /// @notice Safely transfers the ownership of a given Wizard to another address
    /// If the target address is a contract, it must implement `onERC721Received`,
    /// which is called upon a safe transfer, and return the magic value
    /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
    /// the transfer is reverted.
    /// Requires the msg.sender to be the owner, approved, or operator
    /// @param from current owner of the Wizard.
    /// @param to address to receive the ownership of the given Wizard.
    /// @param wizardId ID of the Wizard to be transferred.
    /// @param _data bytes data to send along with a safe transfer check
    function safeTransferFrom(address from, address to, uint256 wizardId, bytes memory _data) public {
        transferFrom(from, to, wizardId);
        require(_checkOnERC721Received(from, to, wizardId, _data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /// @notice Returns whether the specified Wizard exists.
    /// @param wizardId ID of the Wizard to query the existence of..
    /// @return bool whether the Wizard exists.
    function _exists(uint256 wizardId) internal view returns (bool) {
        address owner = wizardsById[wizardId].owner;
        return owner != address(0);
    }

    /// @notice Returns whether the given spender can transfer a given Wizard.
    /// @param spender address of the spender to query
    /// @param wizardId ID of the Wizard to be transferred
    /// @return bool whether the msg.sender is approved for the given Wizard,
    /// is an operator of the owner, or is the owner of the Wizard.
    function _isApprovedOrOwner(address spender, uint256 wizardId) internal view returns (bool) {
        require(_exists(wizardId), "ERC721: operator query for nonexistent token");
        address owner = ownerOf(wizardId);
        return (spender == owner || getApproved(wizardId) == spender || isApprovedForAll(owner, spender));
    }

    /** @dev Internal function to create a new Wizard; reverts if the Wizard ID is taken.
     *       NOTE: This function heavily depends on the internal format of the Wizard struct
     *       and should always be reassessed if anything about that structure changes.
     *  @param wizardId ID of the new Wizard.
     *  @param owner The address that will own the newly conjured Wizard.
     *  @param innatePower The power level associated with the new Wizard.
     *  @param affinity The elemental affinity of the new Wizard.
     */
    function _createWizard(uint256 wizardId, address owner, uint88 innatePower, uint8 affinity) internal {
        require(owner != address(0), "ERC721: mint to the zero address");
        require(!_exists(wizardId), "ERC721: token already minted");
        require(wizardId > 0, "No 0 token allowed");
        require(innatePower > 0, "Wizard power must be non-zero");

        // Create the Wizard!
        wizardsById[wizardId] = Wizard({
            affinity: affinity,
            innatePower: innatePower,
            owner: owner,
            metadata: 0
        });

        ownedWizardsCount[owner]++;

        // Tell the world!
        emit Transfer(address(0), owner, wizardId);
        emit WizardConjured(wizardId, affinity, innatePower);
    }

    /// @notice Internal function to burn a specific Wizard.
    /// Reverts if the Wizard does not exist.
    /// Deprecated, use _burn(uint256) instead.
    /// @param owner owner of the Wizard to burn.
    /// @param wizardId ID of the Wizard being burned
    function _burn(address owner, uint256 wizardId) internal {
        require(ownerOf(wizardId) == owner, "ERC721: burn of token that is not own");

        _clearApproval(wizardId);

        ownedWizardsCount[owner]--;
        // delete the entire object to recover the most gas
        delete wizardsById[wizardId];

        // required for ERC721 compatibility
        emit Transfer(owner, address(0), wizardId);
    }

    /// @notice Internal function to burn a specific Wizard.
    /// Reverts if the Wizard does not exist.
    /// @param wizardId ID of the Wizard being burned
    function _burn(uint256 wizardId) internal {
        _burn(ownerOf(wizardId), wizardId);
    }

    /// @notice Internal function to transfer ownership of a given Wizard to another address.
    /// As opposed to transferFrom, this imposes no restrictions on msg.sender.
    /// @param from current owner of the Wizard.
    /// @param to address to receive the ownership of the given Wizard
    /// @param wizardId ID of the Wizard to be transferred
    function _transferFrom(address from, address to, uint256 wizardId) internal {
        require(ownerOf(wizardId) == from, "ERC721: transfer of token that is not own");
        require(to != address(0), "ERC721: transfer to the zero address");

        _clearApproval(wizardId);

        ownedWizardsCount[from]--;
        ownedWizardsCount[to]++;

        wizardsById[wizardId].owner = to;

        emit Transfer(from, to, wizardId);
    }

    /// @notice Internal function to invoke `onERC721Received` on a target address.
    /// The call is not executed if the target address is not a contract
    /// @param from address representing the previous owner of the given Wizard
    /// @param to target address that will receive the Wizards.
    /// @param wizardId ID of the Wizard to be transferred
    /// @param _data bytes optional data to send along with the call
    /// @return bool whether the call correctly returned the expected magic value
    function _checkOnERC721Received(address from, address to, uint256 wizardId, bytes memory _data)
        internal returns (bool)
    {
        if (!isContract(to)) {
            return true;
        }

        bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, wizardId, _data);
        return (retval == _ERC721_RECEIVED);
    }

    /// @notice Private function to clear current approval of a given Wizard.
    /// @param wizardId ID of the Wizard to be transferred
    function _clearApproval(uint256 wizardId) private {
        if (wizardApprovals[wizardId] != address(0)) {
            wizardApprovals[wizardId] = address(0);
        }
    }
}
/// @title Wizard Non-Fungible Token
/// @notice The basic ERC-721 functionality for storing Cheeze Wizard NFTs.
///     Derived from: https://github.com/OpenZeppelin/openzeppelin-solidity/tree/v2.2.0
function transferFrom(address from, address to, uint256 wizardId) public {
    require(_isApprovedOrOwner(msg.sender, wizardId), "ERC721: transfer caller is not owner nor approved");

    _transferFrom(from, to, wizardId);
}

/// @notice Transfers the ownership of a given Wizard to another address.
/// Usage of this method is discouraged, use `safeTransferFrom` whenever possible.
/// Requires the msg.sender to be the owner, approved, or operator.
/// @param from current owner of the Wizard.
/// @param to address to receive the ownership of the given Wizard.
/// @param wizardId ID of the Wizard to be transferred.