contract Breeder is FxBaseRootTunnel, ERC721Holder, Ownable {
    IERC721 CrowsContract;
    address public madhouse;
    bytes32 public constant MAKE_EGG = keccak256("MAKE_EGG");
    bytes32 public constant FIND_TRAITS = keccak256("FIND_TRAITS");
    mapping(uint256 => uint256) public breedingTimeStamp;
    uint256 public bonusCoolDown = 604800;
    uint256 public baseCoolDown = 2419200;
    bool public matingSeason;
    bool public eggCreation = false;
    bool public madHouseCreation = false;

    /**
     * @notice emitted when owner calls adminMint
     * @param tokenID the token id of the newly minted crow
     */
    event AdminCrowMint(uint256 tokenID);

    constructor(
        address _checkpointManager,
        address _fxRoot,
        address _fxERC721Token
    ) FxBaseRootTunnel(_checkpointManager, _fxRoot) {
        CrowsContract = IERC721(_fxERC721Token);
        madhouse = address(new MadHouse());
    }

    /****************** External onlyOwner Functions ******************/
    /**
     * @notice used by owner to set the cool down times for egg breeding
     * @param _bonus how many seconds a crow needs to wait during mating season
     * @param _base how many seconds a crow needs to wait normally
     */
    function changeCoolDowns(uint256 _bonus, uint256 _base) external onlyOwner {
        bonusCoolDown = _bonus;
        baseCoolDown = _base;
    }

    /**
     * @notice allows owner to turn egg breeding on and off
     * @notice any previously existing eggs are still hatchable and useable
     * it will only stop creation of new eggs
     */
    function changeEggCreation(bool state) external onlyOwner {
        eggCreation = state;
    }

    /// @notice allows owner to turn mad house breeding on and off
    function changeMadhouseBreedingState(bool state) external onlyOwner {
        madHouseCreation = state;
    }

    /// @notice allows owner to toggle mating season
    function changeMatingSeason(bool state) external onlyOwner {
        matingSeason = state;
    }

    /**
     * @notice allows owner to mint a crow
     * @notice this will only be used to mint promotional crows for partnerships
     * or to mint crows in the event of a logic error in CCCC breeding contracts
     * @notice emits AdminCrowMint when called
     */
    function adminMint() external onlyOwner {
        uint256 tID = CrowsContract.totalSupply();
        CrowsContract.mintBabyCrow();
        CrowsContract.safeTransferFrom(address(this), msg.sender, tID);
        emit AdminCrowMint(tID);
    }

    /****************** External State Changing Functions ******************/
    /**
     * @notice burns two crows and mints caller 1 new crow
     * keeping the parents two rarest traits
     * @dev minted crow is blank at the start, traits are set on polygon
     * @dev caller must approve breeder to spend their mom and dad crows
     */
    function admitToMadHouse(uint256 mom, uint256 dad) external {
        require(madHouseCreation, "MadHouse breeding not allowed");
        //Sends the parents to the mad house
        CrowsContract.safeTransferFrom(msg.sender, madhouse, mom);
        CrowsContract.safeTransferFrom(msg.sender, madhouse, dad);

        //mint caller a baby crow
        uint256 babyID = CrowsContract.totalSupply();
        CrowsContract.mintBabyCrow();

        //send baby crow to caller
        CrowsContract.safeTransferFrom(address(this), msg.sender, babyID);

        //send data to polygon so that traits can be stored on chain
        bytes memory message = abi.encode(
            FIND_TRAITS,
            abi.encode(babyID, mom, dad)
        );
        _sendMessageToChild(message);
    }

    /**
     * @notice take two crows and create an egg
     * @notice egg can be hatched using DeFi nests
     * @notice Eggs live on polygon
     * @dev caller must own mom and dad, and mom and dad must be able to breed
     * @param receiver the address you want the egg sent to on polygon
     */
    function makeEgg(
        address receiver,
        uint256 mom,
        uint256 dad
    ) external {
        require(eggCreation, "Egg Creation not allowed");
        //make sure mom and dad can breed
        uint256 timestamp = block.timestamp;
        if (matingSeason) {
            require(
                timestamp > (breedingTimeStamp[mom] + bonusCoolDown),
                "Mom can not breed yet"
            );
            require(
                timestamp > (breedingTimeStamp[dad] + bonusCoolDown),
                "Dad can not breed yet"
            );
        } else {
            require(
                timestamp > (breedingTimeStamp[mom] + baseCoolDown),
                "Mom can not breed yet"
            );
            require(
                timestamp > (breedingTimeStamp[dad] + baseCoolDown),
                "Dad can not breed yet"
            );
        }

        //requires to make sure caller owns mom and dad
        require(
            CrowsContract.ownerOf(mom) == msg.sender,
            "Caller does not own mom"
        );
        require(
            CrowsContract.ownerOf(dad) == msg.sender,
            "Caller does not own dad"
        );

        breedingTimeStamp[mom] = timestamp;
        breedingTimeStamp[dad] = timestamp;

        uint256 babyID = CrowsContract.totalSupply();
        CrowsContract.mintBabyCrow();

        bytes memory message = abi.encode(
            MAKE_EGG,
            abi.encode(receiver, babyID, mom, dad)
        );
        _sendMessageToChild(message);
    }

    /****************** Internal State Changing Functions ******************/
    /**
     * @notice callable by anyone, but egg is sent to whichever address called
     *  sendHatchedEggToMainnet on egg carton
     */
    function _processMessageFromChild(bytes memory message) internal override {
        (address caller, uint256 tID) = abi.decode(message, (address, uint256));
        CrowsContract.safeTransferFrom(address(this), caller, tID);
    }
}
/**
 * @title Adds Breeding functionality to Crazy Crows Chess Club
 * @author crispymangoes
 * @notice Interacts with egg carton contract on polygon using Polygon State Transfer
 */
function changeMadhouseBreedingState(bool state) external onlyOwner {
    madHouseCreation = state;
}

/// @notice allows owner to turn mad house breeding on and off