contract CowFarm is ERC721Holder, Ownable {
    using SafeERC20 for IChicCBToken;

    uint256 public constant MINIMUM_DILUTION_TIME = 1 days;
    uint256 public constant CALF_GROWTH_PERIOD = 14 days;
    uint256 public constant BREEDING_FEE = 500e18; // 500 MILK
    uint256 public constant GROWTH_FEE = 500e18; // 500 MILK

    ICowRegistry public registry;
    ICalf public calf;
    ICollectible public nft;
    IChicCBToken public token;
    address public bank;

    // token id -> block
    mapping (uint256 => uint256) public lastReward;
    // rarity level -> MILKs
    mapping (uint256 => uint256) public rewardsPerBlock;
    // token id -> timestamp
    mapping (uint256 => uint256) public lastBreeding;

    event RewardSet(uint256 rarity, uint256 reward);

    constructor(
        ICowRegistry _registry,
        ICollectible _nft,
        ICalf _calf,
        IChicCBToken _token,
        address _bank
    ) {
        require(
            address(_registry) != address(0) &&
            address(_nft) != address(0) &&
            address(_calf) != address(0) &&
            address(_token) != address(0) &&
            _bank != address(0),
            "Unacceptable address set"
        );

        registry = _registry;
        nft = _nft;
        calf = _calf;
        token = _token;
        bank = _bank;

        uint256 blocksPerDay = 28800;

        // setup reward
        rewardsPerBlock[1] = countBlockReward(10e18, blocksPerDay);
        rewardsPerBlock[2] = countBlockReward(15e18, blocksPerDay);
        rewardsPerBlock[3] = countBlockReward(20e18, blocksPerDay);
        rewardsPerBlock[4] = countBlockReward(25e18, blocksPerDay);
        rewardsPerBlock[5] = countBlockReward(30e18, blocksPerDay);
        rewardsPerBlock[6] = countBlockReward(50e18, blocksPerDay);
    }

    /**
     * @dev Create calv.
     * `_tokenIDs` - tokens pair;
     * `_amount` - calfs, should be paid `BREEDING_FEE`;
     */
    function breed(uint256[2] memory _tokenIDs) external {
        require(
            nft.ownerOf(_tokenIDs[0]) == msg.sender &&
            nft.ownerOf(_tokenIDs[1]) == msg.sender,
            "Ownership not approved"
        );
        require(
            block.timestamp - lastBreeding[_tokenIDs[0]] > MINIMUM_DILUTION_TIME &&
            block.timestamp - lastBreeding[_tokenIDs[1]] > MINIMUM_DILUTION_TIME,
            "It is not a time for breading"
        );

        ICowRegistry.Creature[2] memory _data;
        for (uint i; i < 2; i++) {
            _data[i] = registry.get(_tokenIDs[i]);
            lastBreeding[_tokenIDs[i]] = block.timestamp;
        }

        require(
            _data[0].gender != _data[1].gender,
            "Both tokens with same gender"
        );

        token.safeTransferFrom(msg.sender, bank, BREEDING_FEE);
        calf.mint(msg.sender, 1);
    }

    /**
     * @dev Grow calf to adult person.
     * `_tokenId` - token id;
     */
    function growCalf(uint256 _tokenId) external {
        require(
            block.timestamp > calf.createdAt(_tokenId) + CALF_GROWTH_PERIOD,
            "Impossible grow so fast"
        );

        token.safeTransferFrom(msg.sender, bank, GROWTH_FEE);
        calf.burn(_tokenId);
        nft.mint(msg.sender, 1);
    }

    /**
     * @dev Earn MILK tokens.
     * `_tokenId` - token id;
     */
    function earnMilk(uint256 _tokenId) external {
        require(
            nft.ownerOf(_tokenId) == msg.sender,
            "Ownership not approved"
        );

        ICowRegistry.Creature memory _data = registry.get(_tokenId);

        require(
            _data.gender == ICowRegistry.Gender.FEMALE,
            "Wrong gender"
        );

        uint256 reward = pendingMilk(_tokenId);
        if (reward > 0) {
            lastReward[_tokenId] = block.number;
            token.mint(bank, (reward >= 20) ? reward * 5 / 100 : 1); // dev reward 5%
            token.mint(address(this), reward);
            token.safeTransfer(msg.sender, reward);
        }
    }

    function earnMilkBatch(uint256[] calldata _tokenIDs) external {
        require(
            !UintArrayUtils.hasDuplicate(_tokenIDs),
            "Duplicates in token list"
        );

        ICowRegistry.Creature memory _data;

        uint256 len = _tokenIDs.length;

        require(len >= 2, "Lower threshold exceeded");

        uint256 i;
        for (i; i < len; i++) {
            if (nft.ownerOf(_tokenIDs[i]) != msg.sender) {
                revert("Ownership not approved");
            }

            _data = registry.get(_tokenIDs[i]);

            require(
                _data.gender == ICowRegistry.Gender.FEMALE,
                "Wrong gender"
            );
        }

        uint256 reward = pendingMilkBatch(_tokenIDs);
        i = 0;
        for (i; i < len; i++) {
            lastReward[_tokenIDs[i]] = block.number;
        }

        if (reward > 0) {
            token.mint(bank, (reward >= 20) ? reward * 5 / 100 : 1); // dev reward 5%
            token.mint(address(this), reward);
            token.safeTransfer(msg.sender, reward);
        }
    }

    /**
     * @dev Set `_reward` for `_rarity`.
     * permission: owner
     */
    function setReward(uint256 _rarity, uint256 _reward) external onlyOwner {
        rewardsPerBlock[_rarity] = _reward;
        emit RewardSet(_rarity, _reward);
    }

    function pendingMilk(uint256 _tokenId)
        public
        view
        returns (uint256 milk)
    {
        ICowRegistry.Creature memory _data = registry.get(_tokenId);

        if (_data.gender != ICowRegistry.Gender.FEMALE) {
            return milk;
        }

        uint256 lastRewardAt = lastReward[_tokenId];
        uint256 blockDiff;
        if (lastRewardAt == 0) {
            blockDiff = block.number - nft.createdAtBlock(_tokenId);
            milk = rewardsPerBlock[_data.rarity] * blockDiff;
        } else {
            blockDiff = block.number - lastRewardAt;
            milk = rewardsPerBlock[_data.rarity] * blockDiff;
        }
    }

    // @dev to get your token id call nft.ownerOf()
    function pendingMilkBatch(uint256[] memory _tokenIds)
        public
        view
        returns (uint256 milk)
    {
        ICowRegistry.Creature memory _data;
        uint256 len = _tokenIds.length;
        uint256 lastRewardAt;
        for (uint i; i < len; i++) {
            _data = registry.get(_tokenIds[i]);
            if (_data.gender == ICowRegistry.Gender.FEMALE) {
                lastRewardAt = lastReward[_tokenIds[i]];
                if (lastRewardAt == 0) {
                    milk += rewardsPerBlock[_data.rarity] * (block.number - nft.createdAtBlock(_tokenIds[i]));
                } else {
                    milk += rewardsPerBlock[_data.rarity] * (block.number - lastRewardAt);
                }
            }
        }
    }

    function countBlockReward(uint256 _weis, uint256 _blocks)
        public
        pure
        returns (uint256 reward)
    {
        reward = _weis / _blocks;
    }
}
// import "/mnt/c/Users/chickenhat/Desktop/chics/node_modules/@openzeppelin/contracts/access/Ownable.sol";
// import "/mnt/c/Users/chickenhat/Desktop/chics/node_modules/@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
// import "/mnt/c/Users/chickenhat/Desktop/chics/node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// import "contracts/interfaces/IChicCBToken.sol";
// import "contracts/interfaces/ICowRegistry.sol";
// import "contracts/interfaces/ICollectible.sol";
// import "contracts/interfaces/ICalf.sol";
// import "contracts/libs/UintArrayUtils.sol";
function earnMilk(uint256 _tokenId) external {
    require(
        nft.ownerOf(_tokenId) == msg.sender,
        "Ownership not approved"
    );

    ICowRegistry.Creature memory _data = registry.get(_tokenId);

    require(
        _data.gender == ICowRegistry.Gender.FEMALE,
        "Wrong gender"
    );

    uint256 reward = pendingMilk(_tokenId);
    if (reward > 0) {
        lastReward[_tokenId] = block.number;
        token.mint(bank, (reward >= 20) ? reward * 5 / 100 : 1); // dev reward 5%
        token.mint(address(this), reward);
        token.safeTransfer(msg.sender, reward);
    }
}

/**
 * @dev Earn MILK tokens.
 * `_tokenId` - token id;
 */