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 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 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