contract InauguralGateKeeper is AccessControl, WizardConstants, Address, WizardGuildInterfaceId, TournamentInterfaceId {
    // The Tournament contract.
    TournamentInterface public tournament;

    // The Wizard guild contract.
    // TODO: Replace with the address of the contract once it's deployed.
    WizardGuildInterface public constant WIZARD_GUILD = WizardGuildInterface(address(0x35B7838dd7507aDA69610397A85310AE0abD5034));

    // The Wizard presale contract.
    WizardPresaleInterface public constant WIZARD_PRESALE = WizardPresaleInterface(address(0x2F4Bdafb22bd92AA7b7552d270376dE8eDccbc1E));

    /// @dev The ratio between the cost of a Wizard (in wei) and the power of the Wizard.
    ///      power = cost / MAX_POWER_SCALE
    ///      cost = power * MAX_POWER_SCALE
    uint256 internal constant MAX_POWER_SCALE = 1000;
    uint256 internal tournamentPowerScale;

    function getTournamentPowerScale() external view returns (uint256) {
        return tournamentPowerScale;
    }

    /// @dev The constant conversion factor used for elementalWizardIncrement
    uint256 private constant TENTH_BASIS_POINTS = 100000;

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

    // We pack these values together to save storage costs (and since they are all access together)
    struct WizardCosts {
        /// @dev The cost of Neutral Wizards (in wei).
        uint96 neutralWizardCost;

        /// @dev The cost of the _next_ Elemental Wizard (in wei); increases with each Elemental Wizard
        ///      sold, at the rate defined in `elementalWizardIncrement`
        uint96 elementalWizardCost;

        /// @dev The increment ratio in cost between sequential Elemental Wizards, multiplied by 100k for
        ///      greater granularity (TENTH_BASIS_POINTS)
        uint32 elementalWizardIncrement;
    }

    WizardCosts public wizardCosts;

    /// @param setCooAddress The initial COO address.
    /// @param setCfoAddress The initial CFO address.
    /// @param setNeutralWizardCost The cost of the Neutral Wizards, in wei.
    /// @param setElementalWizardCost The starting cost of the Elemental Wizards, in wei.
    /// @param setElementalWizardIncrement The rate at which the Elemental Wizards cost increases.
    constructor(
        address setCooAddress,
        address payable setCfoAddress,
        uint256 setNeutralWizardCost,
        uint256 setElementalWizardCost,
        uint256 setElementalWizardIncrement)
        public AccessControl(setCooAddress, setCfoAddress)
    {
        wizardCosts = WizardCosts ({
            neutralWizardCost: uint96(setNeutralWizardCost),
            elementalWizardCost: uint96(setElementalWizardCost),
            elementalWizardIncrement: uint32(setElementalWizardIncrement)
        });
    }

    modifier onlyWizardController(uint256 wizardId) {
        require(WIZARD_GUILD.isApprovedOrOwner(msg.sender, wizardId), "Must be Wizard controller");
        _;
    }

    /// @dev The presale contract will be sending Ether directly to this contract,
    ///      so we need it to have a payable fallback.
    function() external payable {
        require(msg.sender == address(WIZARD_PRESALE) || msg.sender == address(tournament), "Don't send funds to GateKeeper");
    }

    /// @notice Registers the address of the Tournament with the GateKeeper. This can't be passed
    ///         into the constructor, because the Tournament itself needs a reference to the GateKeeper
    ///         in its constructor!
    ///
    ///         NOTE: Technically, most of the functions below should have some kind of modifier
    ///               that ensures that the Tournament is set before they are called, but that
    ///               just adds gas. In practice, multiple draft Gatekeeper contracts may be
    ///               deployed, so what matters is that we call this function before sharing
    ///               the address of this contract publicly. No need to enforce that on chain,
    ///               with its associated gas costs!
    function registerTournament(address setTournament) external onlyCOO {
        require(address(tournament) == address(0), "Tournament already registered");
        tournament = TournamentInterface(setTournament);
        require(
            (setTournament != address(0)) &&
            tournament.supportsInterface(_INTERFACE_ID_TOURNAMENT), "Invalid Tournament");

        tournamentPowerScale = tournament.powerScale();
        require(tournamentPowerScale <= MAX_POWER_SCALE, "Power scale too high");
    }

    /// @notice This is it folks, the main event! The way for the world to get new Wizards! Does
    ///         pretty much what it says on the box; let's you conjure a new Wizard with a specified
    ///         elemental affinity. The call must include enough Ether to cover the cost of the new
    ///         Wizard, and any excess is refunded. The power of the Wizard is derived from
    ///         the cost. YOU CAN NOT PAY EXTRA TO RAISE INITIAL POWER LATER. Returns the ID
    ///         of the newly conjured Wizard.
    ///
    ///         While you cannot increase power later, you can conjure some more Wizards!
    /// @param affinity The elemental affinity you want for the Wizard. Valid elements are
    ///        defined in `WizardConstants`, see the constants with names starting `ELEMENT_`.
    ///        ELEMENT_NOTSET is not valid for regular Wizards (unlike Exclusive Wizards).
    function conjureWizard(uint8 affinity) external payable returns (uint256) {
        uint8[] memory affinities = new uint8[](1);

        affinities[0] = affinity;

        uint256[] memory wizardIds = conjureWizardMulti(affinities);

        return wizardIds[0];
    }

    /// @notice A convenience function that allows you to conjure 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"!
    /// @dev This function is careful to bundle all of the external calls (_transferRefund() and onERC721Received())
    ///         at the end of the function to limit the risk of reentrancy attacks.
    /// @param affinities The elemental affinities of the Wizards, can mix and match any valid types.
    ///        Valid elements are defined in `WizardConstants`, see the constants with names starting
    ///        `ELEMENT_`. ELEMENT_NOTSET is not valid for regular Wizards (unlike Exclusive Wizards).
    function conjureWizardMulti(uint8[] memory affinities) public payable
            returns (uint256[] memory wizardIds)
    {
        (uint256 totalCost, uint256 contribution, uint88[] memory powers) = _computeWizardPowers(affinities);

        require(msg.value >= totalCost, "Insufficient funds");

        // Mint the requested Wizards in the guild contract, assigning ownership to the sender
        wizardIds = WIZARD_GUILD.mintWizards(powers, affinities, msg.sender);

        // Enter the new Wizards into the Tournament
        tournament.enterWizards.value(contribution)(wizardIds, powers);

        // Ensure the Wizards are 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 minted because it's allowed for an ERC-721 receiving contract to reject the
        // transfer based on the properties of the token.
        if (isContract(msg.sender)) {
            for (uint256 i = 0; i < wizardIds.length; i++) {
                bytes4 transferAccepted = IERC721Receiver(msg.sender).onERC721Received(msg.sender, address(0), wizardIds[i], "");
                require(transferAccepted == _ERC721_RECEIVED, "Contract owner didn't accept ERC721 transfer");
            }
        }

        // NOTE: _transferRefund() is only safe if msg.value >= totalCost. See the require() near the
        //       beginning of the function to feel better about this fact.
        _transferRefund(totalCost);
    }

    /// @notice Allows for the creation of Exclusive Wizards. This can only be done by the COO, who still has
    ///         to pay for the power imbued in these Wizards! Reverts if the owner address is a smart contract
    ///         that is not ERC-721 aware.
    /// @param wizardIds An array of IDs of the Wizards being conjured.
    /// @param powers The power levels of the Wizards, corresponding 1:1 to Wizard IDs.
    /// @param affinities The elemental affinities of the Wizards, corresponding 1:1 to Wizard IDs.
    ///                   Valid elements are defined in `WizardConstants`, see the constants with
    ///                   names starting `ELEMENT_`. ELEMENT_NOTSET is valid for Exclusive Wizards,
    ///                   unlike regular Wizards.
    /// @param owner The recipient address of the newly conjured Cheeze Wizards.
    function conjureExclusiveMulti(
        uint256[] calldata wizardIds,
        uint256[] calldata powers,
        uint8[] calldata affinities,
        address owner
    )
        external payable onlyCOO
    {
        // Ensure the arrays are all of the same length
        require(wizardIds.length == powers.length && powers.length == affinities.length, "Inconsistent parameter lengths");

        uint256 totalCost = 0;
        uint256 contribution = 0;
        uint88[] memory localPowers = new uint88[](powers.length);

        for (uint256 i = 0; i < powers.length; i++) {
            require(affinities[i] <= MAX_ELEMENT, "Invalid affinity");

            require(powers[i] < (1 << 88), "Invalid power level");
            localPowers[i] = uint88(powers[i]);
            uint256 wizardCost = powerToCost(localPowers[i]);

            totalCost += wizardCost;
            contribution += _potContribution(localPowers[i]);
        }

        require(msg.value >= totalCost, "Insufficient funds");

        // Mint the requested Wizards via the guild contract
        WIZARD_GUILD.mintReservedWizards(wizardIds, localPowers, affinities, owner);

        // Enter the new Wizards into the Tournament
        tournament.enterWizards.value(contribution)(wizardIds, localPowers);

        // Ensure the Wizards are 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 minted 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 transferAccepted = IERC721Receiver(owner).onERC721Received(msg.sender, address(0), wizardIds[i], "");
                require(transferAccepted == _ERC721_RECEIVED, "Contract owner didn't accept ERC721 transfer");
            }
        }

        // NOTE: _transferRefund() is only safe if msg.value >= totalCost. See the require() near the
        //       middle of the function to feel better about this fact.
        _transferRefund(totalCost);
    }

    /// @notice Computes the powers, total cost, and prize pot contribution for an array of new Wizards
    ///         based on the provided affinities. This also checks that the affinity values are valid
    ///         for the Tournament, and updates the elementalWizardCost storage variable as relevant.
    function _computeWizardPowers(uint8[] memory affinities) internal
            returns(uint256 totalCost, uint256 contribution, uint88[] memory powers)
    {
        // Cache the Wizard costs in order to reduce the gas costs from reads and writes to storage.
        uint256 neutralWizardCost = wizardCosts.neutralWizardCost;
        uint256 elementalWizardCost = wizardCosts.elementalWizardCost;
        uint256 elementalWizardIncrement = wizardCosts.elementalWizardIncrement;

        totalCost = 0;
        contribution = 0;
        powers = new uint88[](affinities.length);

        for (uint256 i = 0; i < affinities.length; i++) {
            uint8 affinity = affinities[i];
            uint256 wizardCost;

            require(affinity > ELEMENT_NOTSET && affinity <= MAX_ELEMENT, "Invalid affinity");

            // Determine the price of the Wizard
            if (affinity == ELEMENT_NEUTRAL) {
                wizardCost = neutralWizardCost;
            } else {
                wizardCost = elementalWizardCost;

                // Update the cost of the next Elemental Wizard
                // NOTE: This math can't overflow because the total Ether supply in wei is well less than
                //       2^128. Multiplying a valid cost in wei by some number <100k
                //       cannot possibly overflow 256 bits. As cost is calculated
                //       ourselves (rather than user provided) we know it must
                //       be in the valid range.
                elementalWizardCost += (elementalWizardCost * elementalWizardIncrement) / TENTH_BASIS_POINTS;
            }

            powers[i] = costToPower(wizardCost);

            // IMPORTANT! Mathematically, you'd think we could just compute the pot contribution at the
            // end of the loop, but this risks rounding differences between
            // conjuring Wizards individually compared to conjuring them as a stench.
            contribution += _potContribution(powers[i]);
            totalCost += wizardCost;
        }

        // Update the cached elemental cost to storage. Conveniently this costs very little if
        // the value isn't actually changed.
        wizardCosts.elementalWizardCost = uint96(elementalWizardCost);
    }

    /// @notice Absorbs a number of presale Wizards into the final NFT contract, while also entering them into
    ///         the Tournament. Can handle any number of Wizards in a batch BUT THEY MUST ALL HAVE THE SAME OWNER.
    ///         Callable by anyone.
    /// @param wizardIds The IDs of the presale Wizards; note that all Wizards MUST have the same owner.
    function absorbPresaleWizards(uint256[] calldata wizardIds) external {
        // Bulk fetch the Wizards from the presale contract. Note that this will also delete those Wizards from the
        // presale contract, and will also transfer the funds used to purchase those Wizards to this contract.
        // Obviously, a failed require() statement later in this function will undo that transfer and those deletes.
        (
            address[] memory owners,
             uint256[] memory powers,
             uint8[] memory affinities
        ) = WIZARD_PRESALE.absorbWizardMulti(wizardIds);

        uint256 contribution = 0;
        address theOwner = owners[0];
        uint88[] memory localPowers = new uint88[](powers.length);

        for (uint256 i = 0; i < powers.length; i++) {
            require(owners[i] == theOwner, "All Wizards must have same owner");
            localPowers[i] = uint88(powers[i]);
            contribution += _potContribution(localPowers[i]);
        }

        // Mint the requested Wizards in the guild contract
        WIZARD_GUILD.mintReservedWizards(wizardIds, localPowers, affinities, theOwner);

        // Enter the new Wizards into the Tournament
        tournament.enterWizards.value(contribution)(wizardIds, localPowers);
    }

    /// @notice Revive a tired Wizard so they can duel again. The caller must own
    ///         the Wizard.
    /// @param wizardId The ID of the Wizard to revive.
    function revive(uint256 wizardId) external payable onlyWizardController(wizardId) {
        // We don't need to do any validation here, we can let the Tournament decide
        // if the pot contribution amount derived from msg.value represents a valid
        // power level to use for reviving this Wizard.
        uint88 purchasedPower = costToPower(msg.value);
        uint256 potContributionValue = _potContribution(purchasedPower);

        tournament.revive.value(potContributionValue)(wizardId);
    }

    /// @notice Sets the affinity for a Wizard that doesn't already have its elemental affinity chosen.
    ///         As a rule this is only ever expected to apply to Exclusive Wizards, as regular wizards
    ///         have their affinity set when they are conjured.
    function setAffinity(uint256 wizardId, uint8 newAffinity) external onlyWizardController(wizardId) {
        require(newAffinity > ELEMENT_NOTSET && newAffinity <= MAX_ELEMENT, "Must choose a valid affinity");

        // The guild will enforce the Wizard doesn't already have an affinity set.
        WIZARD_GUILD.setAffinity(wizardId, newAffinity);
    }

    /// @notice Determine the power of a Wizard based on their price.
    /// @param cost The price of the Wizard in wei.
    /// @return The power of the Wizard (cast to uint88).
    function costToPower(uint256 cost) public pure returns (uint88 power) {
        return uint88(cost / MAX_POWER_SCALE);
    }

    /// @param power The power of the Wizard.
    /// @return The cost of the Wizard in wei.
    function powerToCost(uint88 power) public pure returns (uint256 cost) {
        return power * MAX_POWER_SCALE;
    }

    /// @notice Computes the number of wei required to be sent to the Tournament
    ///         in order to match a given power level.
    /// @param wizardPower The power level
    /// @return The prize pot contribution necessary to match the given power
    function _potContribution(uint88 wizardPower) internal view returns (uint256) {
        return wizardPower * tournamentPowerScale;
    }

    /// @notice Allows the CFO to withdraw funds from this contract.
    function withdraw() external onlyCFO {
        // All of the pot contributions go directly into the Tournament contract, so
        // we can safely withdraw everything, with no hold-backs.
        msg.sender.transfer(address(this).balance);
    }


    /// @notice Allows the COO to destroy this contract if it's not needed anymore.
    /// @notice Can't be destoryed if the Tournament still exists.
    function destroy() external onlyCOO {
        require(address(this).balance == 0, "Drain the funds first");
        require(address(tournament) == address(0), "Destroy Tournament first");

        selfdestruct(msg.sender);
    }

    /// @notice Allow the COO to destroy the Tournament contract.
    function destroyTournament() external onlyCOO {
        if (address(tournament) != address(0)) {
            require(tournament.isActive() == false, "Tournament active");
            tournament.destroy();
            tournament = TournamentInterface(0);
        }
    }

    /// @notice Utility function that refunds any overpayment to the sender; smart
    ///      enough to only send the excess if the amount we are returning is more than the
    ///      cost of sending it!
    /// @dev Warning! This does not check for underflows (msg.value < actualPrice) - so
    ///      be sure to call this with correct values!
    /// @param actualPrice The actual price owed for the conjured Wizards.
    function _transferRefund(uint256 actualPrice) private {
        uint256 refund = msg.value - actualPrice;

        // Make sure the amount we're trying to refund is less than the actual cost of sending it!
        // See https://github.com/ethereum/wiki/wiki/Subtleties for magic values costs.  We can
        // safely ignore the 25000 additional gas cost for new accounts, as msg.sender is
        // guaranteed to exist at this point!
        if (refund > (tx.gasprice * (9000+700))) {
            msg.sender.transfer(refund);
        }
    }
}
/// @title The Inaugural Cheeze Wizards Gate Keeper contract
/// @notice The GateKeeper is responsible for determining which Wizards are allowed to enter into
///         a Tournament. This particular GateKeeper is designed to manage the first, official
///         Cheeze Wizards Tournament! It's much more complicated than a typical GateKeeper
///         implementation because it needs to juggle two additional issues that most GateKeepers
///         don't need to deal with: Importing Wizards from the Presale contract and minting tokens
///         to represent newly conjured Wizards.
function _computeWizardPowers(uint8[] memory affinities) internal
        returns(uint256 totalCost, uint256 contribution, uint88[] memory powers)
{
    // Cache the Wizard costs in order to reduce the gas costs from reads and writes to storage.
    uint256 neutralWizardCost = wizardCosts.neutralWizardCost;
    uint256 elementalWizardCost = wizardCosts.elementalWizardCost;
    uint256 elementalWizardIncrement = wizardCosts.elementalWizardIncrement;

    totalCost = 0;
    contribution = 0;
    powers = new uint88[](affinities.length);

    for (uint256 i = 0; i < affinities.length; i++) {
        uint8 affinity = affinities[i];
        uint256 wizardCost;

        require(affinity > ELEMENT_NOTSET && affinity <= MAX_ELEMENT, "Invalid affinity");

        // Determine the price of the Wizard
        if (affinity == ELEMENT_NEUTRAL) {
            wizardCost = neutralWizardCost;
        } else {
            wizardCost = elementalWizardCost;

            // Update the cost of the next Elemental Wizard
            // NOTE: This math can't overflow because the total Ether supply in wei is well less than
            //       2^128. Multiplying a valid cost in wei by some number <100k
            //       cannot possibly overflow 256 bits. As cost is calculated
            //       ourselves (rather than user provided) we know it must
            //       be in the valid range.
            elementalWizardCost += (elementalWizardCost * elementalWizardIncrement) / TENTH_BASIS_POINTS;
        }

        powers[i] = costToPower(wizardCost);

        // IMPORTANT! Mathematically, you'd think we could just compute the pot contribution at the
        // end of the loop, but this risks rounding differences between
        // conjuring Wizards individually compared to conjuring them as a stench.
        contribution += _potContribution(powers[i]);
        totalCost += wizardCost;
    }

    // Update the cached elemental cost to storage. Conveniently this costs very little if
    // the value isn't actually changed.
    wizardCosts.elementalWizardCost = uint96(elementalWizardCost);
}

/// @notice Computes the powers, total cost, and prize pot contribution for an array of new Wizards
///         based on the provided affinities. This also checks that the affinity values are valid
///         for the Tournament, and updates the elementalWizardCost storage variable as relevant.