contract WizardGuild is AccessControl, WizardNFT, WizardGuildInterface, ERC165Query {

    /// @notice Emitted when a new Series is opened or closed.
    event SeriesOpen(uint64 seriesIndex, uint256 reservedIds);
    event SeriesClose(uint64 seriesIndex);

    /// @notice Emitted when metadata is associated with a Wizard
    event MetadataSet(uint256 indexed wizardId, bytes32 metadata);

    /// @notice The index of the current Series (zero-based). When no Series is open, this value
    ///         indicates the index of the _upcoming_ Series. (i.e. it is incremented when the
    ///         Series is closed. This makes it easier to bootstrap the first Series.)
    uint64 internal seriesIndex;

    /// @notice The address which is allowed to mint new Wizards in the current Series. When this
    ///         is set to address(0), there is no open Series.
    address internal seriesMinter;

    /// @notice The index number of the next Wizard to be created (Neutral or Elemental).
    ///         NOTE: There is a subtle distinction between a Wizard "ID" and a Wizard "index".
    ///               We use the term "ID" to refer to a value that includes the Series number in the
    ///               top 64 bits, while the term "index" refers to the Wizard number _within_ its
    ///               Series. This is especially confusing when talking about Wizards in the first
    ///               Series (Series 0), because the two values are identical in that case!
    ///
    ///               |---------------|--------------------------|
    ///               |           Wizard ID (256 bits)           |
    ///               |---------------|--------------------------|
    ///               |  Series Index |      Wizard Index        |
    ///               |   (64 bits)   |       (192 bits)         |
    ///               |---------------|--------------------------|
    uint256 internal nextWizardIndex;

    function getNextWizardIndex() external view returns (uint256) {
        return nextWizardIndex;
    }

    // NOTE: uint256(-1) maps to a value with all bits set, both the << and >> operators will fill
    // in with zeros when acting on an unsigned value. So, "uint256(-1) << 192" resolves to "a bunch
    /// of ones, followed by 192 zeros"
    uint256 internal constant SERIES_OFFSET = 192;
    uint256 internal constant SERIES_MASK = uint256(-1) << SERIES_OFFSET;
    uint256 internal constant INDEX_MASK = uint256(-1) >> 64;

    // The ERC1654 function selector value
    bytes4 internal constant ERC1654_VALIDSIGNATURE = 0x1626ba7e;

    /// @notice The Guild constructor.
    /// @param _cooAddress The COO has the ability to create new Series and to update
    ///         the metadata on the currently open Series (if any). It has no other special
    ///         abilities, and (in particular), ALL Wizards in a closed series can never be
    ///         modified or deleted. If the CEO and COO values are ever set to invalid addresses
    ///        (such as address(1)), then no new Series can ever be created, either.
    constructor(address _cooAddress) public AccessControl(_cooAddress, address(0)) {
    }

    /// @notice Require that a Tournament Series is currently open. For example closing
    ///         a Series does not make sense if none is open.
    /// @dev While in other contracts we use separate checking functions to avoid having the same
    ///      string inlined in multiple places, given this modifier is scarcely used it doesn't seem
    ///      worth the per-call gas cost here.
    modifier duringSeries() {
        require(seriesMinter != address(0), "No series is currently open");
        _;
    }

    /// @notice Require that the caller is the minter of the current series. This implicitely
    ///         requires that a Series is open, or the minter address would be invalid (can never
    ///         be matched).
    /// @dev While in other contracts we use separate checking functions to avoid having the same
    ///      string inlined in multiple places, given this modifier is scarcely used it doesn't seem
    ///      worth the per-call gas cost here.
    modifier onlyMinter() {
        require(msg.sender == seriesMinter, "Only callable by minter");
        _;
    }

    /// @notice Open a new Series of Cheeze Wizards! Can only be called by the COO when no Series is open.
    /// @param minter The address which is allowed to mint Wizards in this series. This contract does not
    ///         assume that the minter is a smart contract, but it will presumably be in the vast majority
    ///         of the cases. A minter has absolute control over the creation of new Wizards in an open
    ///         Series, but CAN NOT manipulate a Series after it has been closed, and CAN NOT manipulate
    ///         any Wizards that don't belong to its own Series. (Even if the same minting address is used
    ///         for multiple Series, the Minter only has power over the currently open Series.)
    /// @param reservedIds The number of IDs (from 1 to reservedIds, inclusive) that are reserved for minting
    ///         reserved Wizards. (We use the term "reserved" here, instead of Exclusive, because there
    ///         are times -- such as during the importation of the Presale -- when we need to reserve a
    ///         block of IDs for Wizards that aren't what a user would think of as "exclusive". In Series
    ///         0, the reserved IDs will include all Exclusive Wizards and Presale Wizards. In other Series
    ///         it might also be the case that the set of "reserved IDs" doesn't exactly match the set of
    ///         "exclusive" IDs.)
    function openSeries(address minter, uint256 reservedIds) external onlyCOO returns (uint64 seriesId) {
        require(seriesMinter == address(0), "A series is already open");
        require(minter != address(0), "Minter address cannot be 0");

        if (seriesIndex == 0) {
            // The Presale contract has stopped sales.
            // The ID of the last wizard sold in the Presale contract is 5974.
            // So the reservedIds for the first series is hardcoded with that ID in order to ensure:
            // 1) The next Wizard minted will have its ID continued from the last Presale Wizard.
            // 2) All the Presale wizards can be minted in this contract with their IDs reserved.
            require(reservedIds == 5974, "Invalid reservedIds for 1st series");
        } else {
            require(reservedIds < 1 << 192, "Invalid reservedIds");
        }

        // NOTE: The seriesIndex is updated when the Series is _closed_, not when it's opened.
        //  (The first Series is Series #0.) So in this function, we just leave the seriesIndex alone.

        seriesMinter = minter;
        nextWizardIndex = reservedIds + 1;

        emit SeriesOpen(seriesIndex, reservedIds);

        return seriesIndex;
    }

    /// @notice Closes the current Wizard Series. Once a Series has been closed, it is forever sealed and
    ///         no more Wizards in that Series can ever be minted! Can only be called by the COO when a Series
    ///         is open.
    ///
    ///    NOTE: A series can be closed by the COO or the Minter. (It's assumed that some minters will
    ///          know when they are done, and others will need to be shut off manually by the COO.)
    function closeSeries() external duringSeries {
        require(
            msg.sender == seriesMinter || msg.sender == cooAddress,
            "Only Minter or COO can close a Series");

        seriesMinter = address(0);
        emit SeriesClose(seriesIndex);

        // Set up the next series.
        seriesIndex += 1;
        nextWizardIndex = 0;
    }

    /// @notice ERC-165 Query Function.
    function supportsInterface(bytes4 interfaceId) public view returns (bool) {
        return interfaceId == _INTERFACE_ID_WIZARDGUILD || super.supportsInterface(interfaceId);
    }

    /// @notice Returns the information associated with the given Wizard
    ///         owner - The address that owns this Wizard
    ///         innatePower - The innate power level of this Wizard, set when minted and entirely
    ///               immutable
    ///         affinity - The Elemental Affinity of this Wizard. For most Wizards, this is set
    ///               when they are minted, but some exclusive Wizards are minted with an affinity
    ///               of 0 (ELEMENT_NOTSET). A Wizard with an NOTSET affinity should NOT be able
    ///               to participate in Tournaments. Once the affinity of a Wizard is set to a non-zero
    ///               value, it can never be changed again.
    ///         metadata - A 256-bit hash of the Wizard's metadata, which is stored off chain. This
    ///               contract doesn't specify format of this hash, nor the off-chain storage mechanism
    ///               but, let's be honest, it's probably an IPFS SHA-256 hash.
    ///
    ///         NOTE: Series zero Wizards have one of four Affinities:  Neutral (1), Fire (2), Water (3)
    ///               or Air (4, sometimes called "Wind" in the code). Future Wizard Series may have
    ///               additional Affinities, and clients of this API should be prepared for that
    ///               eventuality.
    function getWizard(uint256 id) public view returns (address owner, uint88 innatePower, uint8 affinity, bytes32 metadata) {
        Wizard memory wizard = wizardsById[id];
        require(wizard.owner != address(0), "Wizard does not exist");
        (owner, innatePower, affinity, metadata) = (wizard.owner, wizard.innatePower, wizard.affinity, wizard.metadata);
    }

    /// @notice A function to be called that conjures a whole bunch of Wizards at once! You know how
    ///         there's "a pride of lions", "a murder of crows", and "a parliament of owls"? Well, with this
    ///         here function you can conjure yourself "a stench of Cheeze Wizards"!
    ///
    ///         Unsurprisingly, this method can only be called by the registered minter for a Series.
    /// @dev This function DOES NOT CALL onERC721Received() as required by the ERC-721 standard. It is
    ///         REQUIRED that the Minter calls onERC721Received() after calling this function. The following
    ///         code snippet should suffice:
    ///                 // Ensure the Wizard is being assigned to an ERC-721 aware address (either an external address,
    ///                 // or a smart contract that implements onERC721Received()). We must call onERC721Received for
    ///                 // each token created because it's allowed for an ERC-721 receiving contract to reject the
    ///                 // transfer based on the properties of the token.
    ///                 if (isContract(owner)) {
    ///                     for (uint256 i = 0; i < wizardIds.length; i++) {
    ///                         bytes4 retval = IERC721Receiver(owner).onERC721Received(owner, address(0), wizardIds[i], "");
    ///                         require(retval == _ERC721_RECEIVED, "Contract owner didn't accept ERC721 transfer");
    ///                     }
    ///                 }
    ///        Although it would be convenient for mintWizards to call onERC721Received, it opens us up to potential
    ///        reentrancy attacks if the Minter needs to do more state updates after mintWizards() returns.
    /// @param powers the power level of each wizard
    /// @param affinities the Elements of the wizards to create
    /// @param owner the address that will own the newly created Wizards
    function mintWizards(
        uint88[] calldata powers,
        uint8[] calldata affinities,
        address owner
    ) external onlyMinter returns (uint256[] memory wizardIds)
    {
        require(affinities.length == powers.length, "Inconsistent parameter lengths");

        // allocate result array
        wizardIds = new uint256[](affinities.length);

        // We take this storage variables, and turn it into a local variable for the course
        // of this loop to save about 5k gas per wizard.
        uint256 tempWizardId = (uint256(seriesIndex) << SERIES_OFFSET) + nextWizardIndex;

        for (uint256 i = 0; i < affinities.length; i++) {
            wizardIds[i] = tempWizardId;
            tempWizardId++;

            _createWizard(wizardIds[i], owner, powers[i], affinities[i]);
        }

        nextWizardIndex = tempWizardId & INDEX_MASK;
    }

    /// @notice A function to be called that mints a Series of Wizards in the reserved ID range, can only
    ///         be called by the Minter for this Series.
    /// @dev This function DOES NOT CALL onERC721Received() as required by the ERC-721 standard. It is
    ///         REQUIRED that the Minter calls onERC721Received() after calling this function. See the note
    ///         above on mintWizards() for more info.
    /// @param wizardIds the ID values to use for each Wizard, must be in the reserved range of the current Series.
    /// @param powers the power level of each Wizard.
    /// @param affinities the Elements of the Wizards to create.
    /// @param owner the address that will own the newly created Wizards.
    function mintReservedWizards(
        uint256[] calldata wizardIds,
        uint88[] calldata powers,
        uint8[] calldata affinities,
        address owner
    )
    external onlyMinter
    {
        require(
            wizardIds.length == affinities.length &&
            wizardIds.length == powers.length, "Inconsistent parameter lengths");

        for (uint256 i = 0; i < wizardIds.length; i++) {
            uint256 currentId = wizardIds[i];

            require((currentId & SERIES_MASK) == (uint256(seriesIndex) << SERIES_OFFSET), "Wizards not in current series");
            require((currentId & INDEX_MASK) > 0, "Wizards id cannot be zero");

            // Ideally, we would compare the requested Wizard index against the reserved range directly. However,
            // it's a bit wasteful to spend storage on a reserved range variable when we can combine some known
            // true facts instead:
            //         - nextWizardIndex is initialized to reservedRange + 1 when the Series was opened
            //         - nextWizardIndex is only incremented when a new Wizard is created
            //         - therefore, the only empty Wizard IDs less than nextWizardIndex are in the reserved range.
            //         - _conjureWizard() will abort if we try to reuse an ID.
            // Combining all of the above, we know that, if the requested index is less than the next index, it
            // either points to a reserved slot or an occupied slot. Trying to reuse an occupied slot will fail,
            // so just checking against nextWizardIndex is sufficient to ensure we're pointing at a reserved slot.
            require((currentId & INDEX_MASK) < nextWizardIndex, "Wizards not in reserved range");

            _createWizard(currentId, owner, powers[i], affinities[i]);
        }
    }

    /// @notice Sets the metadata values for a list of Wizards. The metadata for a Wizard can only be set once,
    ///         can only be set by the COO or Minter, and can only be set while the Series is still open. Once
    ///         a Series is closed, the metadata is locked forever!
    /// @param wizardIds the ID values of the Wizards to apply metadata changes to.
    /// @param metadata the raw metadata values for each Wizard. This contract does not define how metadata
    ///         should be interpreted, but it is likely to be a 256-bit hash of a complete metadata package
    ///         accessible via IPFS or similar.
    function setMetadata(uint256[] calldata wizardIds, bytes32[] calldata metadata) external duringSeries {
        require(msg.sender == seriesMinter || msg.sender == cooAddress, "Only Minter or COO can set metadata");
        require(wizardIds.length == metadata.length, "Inconsistent parameter lengths");

        for (uint256 i = 0; i < wizardIds.length; i++) {
            uint256 currentId = wizardIds[i];
            bytes32 currentMetadata = metadata[i];

            require((currentId & SERIES_MASK) == (uint256(seriesIndex) << SERIES_OFFSET), "Wizards not in current series");

            require(wizardsById[currentId].metadata == bytes32(0), "Metadata already set");

            require(currentMetadata != bytes32(0), "Invalid metadata");

            wizardsById[currentId].metadata = currentMetadata;

            emit MetadataSet(currentId, currentMetadata);
        }
    }

    /// @notice Sets the affinity for a Wizard that doesn't already have its elemental affinity chosen.
    ///         Only usable for Exclusive Wizards (all non-Exclusives must have their affinity chosen when
    ///         conjured.) Even Exclusives can't change their affinity once it's been chosen.
    ///
    ///         NOTE: This function can only be called by the Series minter, and (therefore) only while the
    ///         Series is open. A Wizard that has no affinity when a Series is closed will NEVER have an Affinity.
    /// @param wizardId The ID of the Wizard to update affinity of.
    /// @param newAffinity The new affinity of the Wizard.
    function setAffinity(uint256 wizardId, uint8 newAffinity) external onlyMinter {
        require((wizardId & SERIES_MASK) == (uint256(seriesIndex) << SERIES_OFFSET), "Wizard not in current series");

        Wizard storage wizard = wizardsById[wizardId];

        require(wizard.affinity == ELEMENT_NOTSET, "Affinity can only be chosen once");

        // set the affinity
        wizard.affinity = newAffinity;

        // Tell the world this wizards now has an affinity!
        emit WizardAffinityAssigned(wizardId, newAffinity);
    }

    /// @notice Returns true if the given "spender" address is allowed to manipulate the given token
    ///         (either because it is the owner of that token, has been given approval to manage that token)
    function isApprovedOrOwner(address spender, uint256 tokenId) external view returns (bool) {
        return _isApprovedOrOwner(spender, tokenId);
    }

    /// @notice Verifies that a given signature represents authority to control the given Wizard ID,
    ///         reverting otherwise. It handles three cases:
    ///             - The simplest case: The signature was signed with the private key associated with
    ///               an external address that is the owner of this Wizard.
    ///             - The signature was generated with the private key associated with an external address
    ///               that is "approved" for working with this Wizard ID. (See the Wizard Guild and/or
    ///               the ERC-721 spec for more information on "approval".)
    ///             - The owner or approval address (as in cases one or two) is a smart contract
    ///               that conforms to ERC-1654, and accepts the given signature as being valid
    ///               using its own internal logic.
    ///
    ///        NOTE: This function DOES NOT accept a signature created by an address that was given "operator
    ///               status" (as granted by ERC-721's setApprovalForAll() functionality). Doing so is
    ///               considered an extreme edge case that can be worked around where necessary.
    /// @param wizardId The Wizard ID whose control is in question
    /// @param hash The message hash we are authenticating against
    /// @param sig the signature data; can be longer than 65 bytes for ERC-1654
    function verifySignature(uint256 wizardId, bytes32 hash, bytes memory sig) public view {
        // First see if the signature belongs to the owner (the most common case)
        address owner = ownerOf(wizardId);

        if (_validSignatureForAddress(owner, hash, sig)) {
            return;
        }

        // Next check if the signature belongs to the approved address
        address approved = getApproved(wizardId);

        if (_validSignatureForAddress(approved, hash, sig)) {
            return;
        }

        revert("Invalid signature");
    }

    /// @notice Convenience function that verifies signatures for two wizards using equivalent logic to
    ///         verifySignature(). Included to save on cross-contract calls in the common case where we
    ///         are verifying the signatures of two Wizards who wish to enter into a Duel.
    /// @param wizardId1 The first Wizard ID whose control is in question
    /// @param wizardId2 The second Wizard ID whose control is in question
    /// @param hash1 The message hash we are authenticating against for the first Wizard
    /// @param hash2 The message hash we are authenticating against for the first Wizard
    /// @param sig1 the signature data corresponding to the first Wizard; can be longer than 65 bytes for ERC-1654
    /// @param sig2 the signature data corresponding to the second Wizard; can be longer than 65 bytes for ERC-1654
    function verifySignatures(
        uint256 wizardId1,
        uint256 wizardId2,
        bytes32 hash1,
        bytes32 hash2,
        bytes calldata sig1,
        bytes calldata sig2) external view
    {
        verifySignature(wizardId1, hash1, sig1);
        verifySignature(wizardId2, hash2, sig2);
    }

    /// @notice An internal function that checks if a given signature is a valid signature for a
    ///         specific address on a particular hash value. Checks for ERC-1654 compatibility
    ///         first (where the possibleSigner is a smart contract that implements its own
    ///         signature validation), and falls back to ecrecover() otherwise.
    function _validSignatureForAddress(address possibleSigner, bytes32 hash, bytes memory signature)
        internal view returns(bool)
    {
        if (possibleSigner == address(0)) {
            // The most basic Bozo check: The zero address can never be a valid signer!
            return false;
        } else if (Address.isContract(possibleSigner)) {
            // If the address is a contract, it either implements ERC-1654 (and will validate the signature
            // itself), or we have no way of confirming that this signature matches this address. In other words,
            // if this address is a contract, there's no point in "falling back" to ecrecover().
            if (doesContractImplementInterface(possibleSigner, ERC1654_VALIDSIGNATURE)) {
                // cast to ERC1654
                ERC1654 tso = ERC1654(possibleSigner);
                bytes4 result = tso.isValidSignature(keccak256(abi.encodePacked(hash)), signature);
                if (result == ERC1654_VALIDSIGNATURE) {
                    return true;
                }
            }

            return false;
        } else {
            // Not a contract, check for a match against an external address
            // assume EIP 191 signature here
            (bytes32 r, bytes32 s, uint8 v) = SigTools._splitSignature(signature);
            address signer = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)), v, r, s);

            // Note: Signer could be address(0) here, but we already checked that possibleSigner isn't zero
            return (signer == possibleSigner);
        }
    }

}
/// @title The master organization behind all Cheeze Wizardry. The source of all them Wiz.
function verifySignature(uint256 wizardId, bytes32 hash, bytes memory sig) public view {
    // First see if the signature belongs to the owner (the most common case)
    address owner = ownerOf(wizardId);

    if (_validSignatureForAddress(owner, hash, sig)) {
        return;
    }

    // Next check if the signature belongs to the approved address
    address approved = getApproved(wizardId);

    if (_validSignatureForAddress(approved, hash, sig)) {
        return;
    }

    revert("Invalid signature");
}

/// @notice Verifies that a given signature represents authority to control the given Wizard ID,
///         reverting otherwise. It handles three cases:
///             - The simplest case: The signature was signed with the private key associated with
///               an external address that is the owner of this Wizard.
///             - The signature was generated with the private key associated with an external address
///               that is "approved" for working with this Wizard ID. (See the Wizard Guild and/or
///               the ERC-721 spec for more information on "approval".)
///             - The owner or approval address (as in cases one or two) is a smart contract
///               that conforms to ERC-1654, and accepts the given signature as being valid
///               using its own internal logic.
///
///        NOTE: This function DOES NOT accept a signature created by an address that was given "operator
///               status" (as granted by ERC-721's setApprovalForAll() functionality). Doing so is
///               considered an extreme edge case that can be worked around where necessary.
/// @param wizardId The Wizard ID whose control is in question
/// @param hash The message hash we are authenticating against
/// @param sig the signature data; can be longer than 65 bytes for ERC-1654