contract DragonBreeding is DragonOwnership {

    /// @dev The Pregnant event is fired when two dragons successfully breed and the pregnancy timer begins for the matron.
    event Pregnant(address owner, uint256 matronId, uint256 sireId);

    /// @notice The minimum payment required to use breedWithAuto(). This fee goes towards
    ///  the gas cost paid by whatever calls giveBirth(), and can be dynamically updated by
    ///  the CIO role as the gas price changes.
    uint256 public autoBirthFee = 2 finney;

    // Keeps track of number of pregnant dragons.
    uint256 public pregnantDragons;

    uint32 public BREEDING_LIMIT = 3;
    mapping(uint256 => uint64) breeding;

    /// @dev The address of the sibling contract that is used to implement the sooper-sekret genetic combination algorithm.
    //GeneScienceInterface public geneScience;

    /// @dev Update the address of the genetic contract, can only be called by the CEO.
    /// @param _address An address of a GeneScience contract instance to be used from this point forward.
    //function setGeneScienceAddress(address _address) external onlyCEO {
    //    GeneScienceInterface candidateContract = GeneScienceInterface(_address);

    // NOTE: verify that a contract is what we expect - https://github.com/Lunyr/crowdsale-contracts/blob/cfadd15986c30521d8ba7d5b6f57b4fefcc7ac38/contracts/LunyrToken.sol#L117
    //    require(candidateContract.isGeneScience());

    // Set the new contract address
    //    geneScience = candidateContract;
    //}

    /// @dev Checks that a given Dragon is able to breed. Requires that the
    ///  current cooldown is finished (for sires) and also checks that there is
    ///  no pending pregnancy.
    function _isReadyToBreed(Dragon storage _dragon) internal view returns (bool) {
        // In addition to checking the cooldownEndBlock, we also need to check to see if
        // the dragon has a pending birth; there can be some period of time between the end
        // of the pregnacy timer and the birth event.
        return (_dragon.siringWithId == 0);
    }

    /// @dev Check if a sire has authorized breeding with this matron. True if both sire
    ///  and matron have the same owner, or if the sire has given siring permission to
    ///  the matron's owner (via approveSiring()).
    function _isSiringPermitted(uint256 _sireId, uint256 _matronId) internal view returns (bool) {
        address matronOwner = dragonIndexToOwner[_matronId];
        address sireOwner = dragonIndexToOwner[_sireId];

        // Siring is okay if they have same owner, or if the matron's owner was given
        // permission to breed with this sire.
        return (matronOwner == sireOwner || sireAllowedToAddress[_sireId] == matronOwner);
    }



    /// @notice Grants approval to another user to sire with one of your Dragons.
    /// @param _addr The address that will be able to sire with your Dragon. Set to
    ///  address(0) to clear all siring approvals for this Dragon.
    /// @param _sireId A Dragon that you own that _addr will now be able to sire with.
    function approveSiring(address _addr, uint256 _sireId)
    external
    whenNotPaused
    {
        require(_owns(msg.sender, _sireId));
        sireAllowedToAddress[_sireId] = _addr;
    }

    /// @dev Updates the minimum payment required for calling giveBirthAuto(). Can only
    ///  be called by the CMO address. (This fee is used to offset the gas cost incurred
    ///  by the autobirth daemon).
    function setAutoBirthFee(uint256 val) external onlyCLevel {
        autoBirthFee = val;
    }

    /// @dev Checks to see if a given Dragon is pregnant and (if so) if the gestation period has passed.
    function _isReadyToGiveBirth(Dragon storage _matron) private view returns (bool) {
        return (_matron.siringWithId != 0);
    }

    /// @notice Checks that a given dragon is able to breed (i.e. it is not pregnant or
    ///  in the middle of a siring cooldown).
    /// @param _dragonId reference the id of the dragon, any user can inquire about it
    function isReadyToBreed(uint256 _dragonId)
    public
    view
    returns (bool)
    {
        require(_dragonId > 0);
        Dragon storage dragon = dragons[_dragonId];
        return _isReadyToBreed(dragon);
    }

    /// @dev Checks whether a dragon is currently pregnant.
    /// @param _dragonId reference the id of the dragon, any user can inquire about it
    function isPregnant(uint256 _dragonId)
    public
    view
    returns (bool)
    {
        require(_dragonId > 0);
        // A dragon is pregnant if and only if this field is set
        return dragons[_dragonId].siringWithId != 0;
    }

    /// @dev Internal check to see if a given sire and matron are a valid mating pair. DOES NOT
    /// check ownership permissions (that is up to the caller).
    /// @param _matron A reference to the Dragon struct of the potential matron.
    /// @param _matronId The matron's ID.
    /// @param _sire A reference to the Dragon struct of the potential sire.
    /// @param _sireId The sire's ID
    function _isValidMatingPair(
        Dragon storage _matron,
        uint256 _matronId,
        Dragon storage _sire,
        uint256 _sireId
    )
    private
    view
    returns(bool)
    {
        if(breeding[_matronId] >= BREEDING_LIMIT) {
            return false;
        }

        uint256 sireElement = _sire.dna / 1e34;
        uint256 matronElement = _matron.dna / 1e34;

        if (sireElement != matronElement) {
          return false;
        }

        // A Dragon can't breed with itself!
        if (_matronId == _sireId) {
            return false;
        }

        // Dragons can't breed with their parents.
        if (_matron.matronId == _sireId || _matron.sireId == _sireId) {
            return false;
        }

        if (_sire.matronId == _matronId || _sire.sireId == _matronId) {
            return false;
        }

        // We can short circuit the sibling check (below) if either dragon is first generation (has a matron ID of zero).
        if (_sire.matronId == 0 || _matron.matronId == 0) {
            return true;
        }

        // Dragons can't breed with full or half siblings.
        if (_sire.matronId == _matron.matronId || _sire.matronId == _matron.sireId) {
            return false;
        }
        if (_sire.sireId == _matron.matronId || _sire.sireId == _matron.sireId) {
            return false;
        }

        // Everything seems cool! Let's get DTF.
        return true;
    }

    /// @dev Internal check to see if a given sire and matron are a valid mating pair for
    ///  breeding via auction (i.e. skips ownership and siring approval checks).
    function _canBreedWithViaAuction(uint256 _matronId, uint256 _sireId)
    internal
    view
    returns (bool)
    {
        Dragon storage matron = dragons[_matronId];
        Dragon storage sire = dragons[_sireId];
        return _isValidMatingPair(matron, _matronId, sire, _sireId);
    }

    /// @notice Checks to see if two dragons can breed together, including checks for
    ///  ownership and siring approvals. Does NOT check that both dragons are ready for
    ///  breeding (i.e. breedWith could still fail until the cooldowns are finished).
    ///  TODO: Shouldn't this check pregnancy and cooldowns?!?
    /// @param _matronId The ID of the proposed matron.
    /// @param _sireId The ID of the proposed sire.
    function canBreedWith(uint256 _matronId, uint256 _sireId)
    external
    view
    returns(bool)
    {
        require(_matronId > 0);
        require(_sireId > 0);
        Dragon storage matron = dragons[_matronId];
        Dragon storage sire = dragons[_sireId];
        return _isValidMatingPair(matron, _matronId, sire, _sireId) &&
        _isSiringPermitted(_sireId, _matronId);
    }

    /// @dev Internal utility function to initiate breeding, assumes that all breeding
    ///  requirements have been checked.
    function _breedWith(uint256 _matronId, uint256 _sireId) internal {
        // Grab a reference to the Dragons from storage.
        // Dragon storage sire = dragons[_sireId];
        Dragon storage matron = dragons[_matronId];

        // Mark the matron as pregnant, keeping track of who the sire is.
        matron.siringWithId = uint32(_sireId);

        // Trigger the cooldown for both parents.
        // _triggerCooldown(sire);
        // _triggerCooldown(matron);

        // Clear siring permission for both parents. This may not be strictly necessary but it's likely to avoid confusion!
        delete sireAllowedToAddress[_matronId];
        delete sireAllowedToAddress[_sireId];

        // Every time a dragon gets pregnant, counter is incremented.
        pregnantDragons++;

        // Emit the pregnancy event.
        emit Pregnant(dragonIndexToOwner[_matronId], _matronId, _sireId);
    }

    /// @notice Breed a Dragon you own (as matron) with a sire that you own, or for which you
    ///  have previously been given Siring approval. Will either make your dragon pregnant, or will
    ///  fail entirely. Requires a pre-payment of the fee given out to the first caller of giveBirth()
    /// @param _matronId The ID of the Dragon acting as matron (will end up pregnant if successful)
    /// @param _sireId The ID of the Dragon acting as sire (will begin its siring cooldown if successful)
    function breedWithAuto(uint256 _matronId, uint256 _sireId)
    external
    payable
    whenNotPaused
    {
        // Checks for payment.
        require(msg.value >= autoBirthFee);

        // Caller must own the matron.
        require(_owns(msg.sender, _matronId));

        // Neither sire nor matron are allowed to be on auction during a normal
        // breeding operation, but we don't need to check that explicitly.
        // For matron: The caller of this function can't be the owner of the matron
        //   because the owner of a Dragon on auction is the auction house, and the
        //   auction house will never call breedWith().
        // For sire: Similarly, a sire on auction will be owned by the auction house
        //   and the act of transferring ownership will have cleared any oustanding
        //   siring approval.
        // Thus we don't need to spend gas explicitly checking to see if either dragon
        // is on auction.

        // Check that matron and sire are both owned by caller, or that the sire
        // has given siring permission to caller (i.e. matron's owner).
        // Will fail for _sireId = 0
        require(_isSiringPermitted(_sireId, _matronId));

        // Grab a reference to the potential matron
        Dragon storage matron = dragons[_matronId];

        // Make sure matron isn't pregnant, or in the middle of a siring cooldown
        require(_isReadyToBreed(matron));

        // Grab a reference to the potential sire
        Dragon storage sire = dragons[_sireId];

        // Make sure sire isn't pregnant, or in the middle of a siring cooldown
        require(_isReadyToBreed(sire));

        // Update the breedTime
        matron.breedTime = uint64(now);

        // Test that these dragons are a valid mating pair.
        require(_isValidMatingPair(
                matron,
                _matronId,
                sire,
                _sireId
            ));


        // All checks passed, dragon gets pregnant!
        _breedWith(_matronId, _sireId);
    }

    /// @notice Have a pregnant Dragon give birth!
    /// @param _matronId A Dragon ready to give birth.
    /// @param _dna Dragon's DNA
    /// @param _agility Dragon's agility initial value
    /// @param _strength Dragon's Strenght initial value
    /// @param _intelligence Dragon's Intelligence initial value
    /// @param _runelevel Dragon's Rune Level initial value
    /// @return The Dragon ID of the new dragon.
    /// @dev Looks at a given Dragon and, if pregnant and if the gestation period has passed,
    ///  combines the genes of the two parents to create a new dragon. The new Dragon is assigned
    ///  to the current owner of the matron. Upon successful completion, both the matron and the
    ///  new dragon will be ready to breed again. Note that anyone can call this function (if they
    ///  are willing to pay the gas!), but the new dragon always goes to the mother's owner.
    function giveBirth(uint256 _matronId, uint256 _dna, uint64 _agility, uint64 _strength, uint64 _intelligence, uint64 _runelevel)
    external
    whenNotPaused
    onlyCOO
    returns(uint256)
    {
        // Grab a reference to the matron in storage.
        Dragon storage matron = dragons[_matronId];

        // Check that the dragon is a valid dragon.
        require(matron.birthTime != 0);

        // Check that the matron is pregnant, and that its time has come!
        require(_isReadyToGiveBirth(matron));

        // Grab a reference to the sire in storage.
        uint256 sireId = matron.siringWithId;
        Dragon storage sire = dragons[sireId];

        // Determine the higher generation number of the two parents
        uint32 parentGen = matron.generation;
        if (sire.generation > matron.generation) {
            parentGen = sire.generation;
        }

        // Call the sooper-sekret gene mixing operation.
        uint256 matronId = _matronId;
        uint64 agility = _agility;
        uint64 strength = _strength;
        uint64 intelligence = _intelligence;
        uint64 runelevel = _runelevel;

        uint256 childDNA = _dna;

        // Make the new dragon!
        address owner = dragonIndexToOwner[matronId];
        //uint256 dragonId = _createDragon(_matronId, matron.siringWithId, parentGen + 1, childDNA, _agility, _strength, _intelligence, _runelevel, owner);
        uint256 dragonId = _createDragon(matronId, sireId, parentGen + 1, childDNA, agility, strength, intelligence, runelevel, owner);

        //increment the breeding for the matron
        breeding[matronId]++;

        // Clear the reference to sire from the matron (REQUIRED! Having siringWithId
        // set is what marks a matron as being pregnant.)
        delete matron.siringWithId;

        // Every time a dragon gives birth counter is decremented.
        pregnantDragons--;

        // Send the balance fee to the person who made birth happen.
        //msg.sender.send(autoBirthFee);
        msg.sender.transfer(autoBirthFee);

        // return the new dragon's ID
        return dragonId;
    }

    function getPregnantDragons() external view returns(uint256[] memory pregnantDragonsList) {

        if (pregnantDragons == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](pregnantDragons);
            uint256 totalDragons = totalSupply();
            uint256 resultIndex = 0;

             uint256 dragonId;

            for (dragonId = 1; dragonId <= totalDragons; dragonId++) {
                if (isPregnant(dragonId)) {
                    result[resultIndex] = dragonId;
                    resultIndex++;
                }
            }

            return result;
        }
    }

    function setBreedingLimit(uint32 _value) external onlyCLevel {
        BREEDING_LIMIT = _value;
    }
}
/// @title DragonCore that manages Dragon siring, gestation, and birth.
/// @author Zynappse Corporation (https://www.zynapse.com)
/// @dev See the DragonCore contract documentation to understand how the various contract facets are arranged.
function _breedWith(uint256 _matronId, uint256 _sireId) internal {
    // Grab a reference to the Dragons from storage.
    // Dragon storage sire = dragons[_sireId];
    Dragon storage matron = dragons[_matronId];

    // Mark the matron as pregnant, keeping track of who the sire is.
    matron.siringWithId = uint32(_sireId);

    // Trigger the cooldown for both parents.
    // _triggerCooldown(sire);
    // _triggerCooldown(matron);

    // Clear siring permission for both parents. This may not be strictly necessary but it's likely to avoid confusion!
    delete sireAllowedToAddress[_matronId];
    delete sireAllowedToAddress[_sireId];

    // Every time a dragon gets pregnant, counter is incremented.
    pregnantDragons++;

    // Emit the pregnancy event.
    emit Pregnant(dragonIndexToOwner[_matronId], _matronId, _sireId);
}

/// @dev Internal utility function to initiate breeding, assumes that all breeding
///  requirements have been checked.