contract FarmGenerator is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /* ============ Struct ============ */

    struct FarmParameters {
        uint256 bonusBlocks;
        uint256 totalBonusReward;
        uint256 numBlocks;
        uint256 endBlock;
        uint256 requiredAmount;
    }

    /* ============ State Variables ============ */

    IFarmFactory public factory;

    /* ============ Events ============ */

    event FarmCreated(
        IERC20 rewardToken,
        uint256 amount,
        IERC20 lpToken,
        uint256 blockReward,
        uint256 startBlock,
        uint256 bonusEndBlock,
        uint256 bonus
    );

    /* ============ Constructor ============ */

    /**
     * Store factory instance.
     *
     * @param _factory              Instance of Farm Factory contract
     */
    constructor(IFarmFactory _factory) public {
        factory = _factory;
    }

    /**
     * Determine the endBlock based on inputs. Used on the front end to show the exact settings the Farm contract
     * will be deployed with
     *
     * @param _amount               Total sum of reward
     * @param _blockReward          Reward per block
     * @param _startBlock           Block number to start reward
     * @param _bonusEndBlock        Block number to end the bonus reward
     * @param _bonus                Bonus multipler which will be applied until bonus end block
     */
    function determineEndBlock(
        uint256 _amount,
        uint256 _blockReward,
        uint256 _startBlock,
        uint256 _bonusEndBlock,
        uint256 _bonus
    )
        public
        pure
        returns (uint256, uint256)
    {
        FarmParameters memory params;
        params.bonusBlocks = _bonusEndBlock.sub(_startBlock);
        params.totalBonusReward = params.bonusBlocks.mul(_bonus).mul(_blockReward);
        params.numBlocks = _amount.sub(params.totalBonusReward).div(_blockReward);
        params.endBlock = params.numBlocks.add(params.bonusBlocks).add(_startBlock);

        uint256 nonBonusBlocks = params.endBlock.sub(_bonusEndBlock);
        uint256 effectiveBlocks = params.bonusBlocks.mul(_bonus).add(nonBonusBlocks);
        uint256 requiredAmount = _blockReward.mul(effectiveBlocks);
        return (params.endBlock, requiredAmount);
    }

    /**
     * Determine the blockReward based on inputs specifying an end date. Used on the front end to show the exact settings
     * the Farm contract will be deployed with
     *
     * @param _amount               Total sum of reward
     * @param _startBlock           Block number to start reward
     * @param _bonusEndBlock        Block number to end the bonus reward
     * @param _bonus                Bonus multipler which will be applied until bonus end block
     * @param _endBlock             Block number to end reward
     */
    function determineBlockReward(
        uint256 _amount,
        uint256 _startBlock,
        uint256 _bonusEndBlock,
        uint256 _bonus,
        uint256 _endBlock
    )
        public
        pure
        returns (uint256, uint256)
    {
        uint256 bonusBlocks = _bonusEndBlock.sub(_startBlock);
        uint256 nonBonusBlocks = _endBlock.sub(_bonusEndBlock);
        uint256 effectiveBlocks = bonusBlocks.mul(_bonus).add(nonBonusBlocks);
        uint256 blockReward = _amount.div(effectiveBlocks);
        uint256 requiredAmount = blockReward.mul(effectiveBlocks);
        return (blockReward, requiredAmount);
    }

    /**
     * Creates a new Farm contract and registers it in the farm factory. Farming rewards are locked in the farm
     *
     * @param _rewardToken          Instance of reward token contract
     * @param _amount               Total sum of reward
     * @param _lpToken              Instance of LP token contract
     * @param _blockReward          Reward per block
     * @param _startBlock           Block number to start reward
     * @param _bonusEndBlock        Block number to end the bonus reward
     * @param _bonus                Bonus multipler which will be applied until bonus end block
     * @param _manager              Manager of the farm
     */
    function createFarm(
        IERC20 _rewardToken,
        uint256 _amount,
        IERC20 _lpToken,
        uint256 _blockReward,
        uint256 _startBlock,
        uint256 _bonusEndBlock,
        uint256 _bonus,
        address _manager
    )
        public
        onlyOwner
        returns (address)
    {
        require(_startBlock > block.number, "START"); // ideally at least 24 hours more to give farmers time
        require(_bonus > 0, "BONUS");
        require(address(_rewardToken) != address(0), "TOKEN");
        require(_blockReward > 1000, "BR"); // minimum 1000 divisibility per block reward

        (uint256 endBlock, uint256 requiredAmount) = determineEndBlock(_amount, _blockReward, _startBlock, _bonusEndBlock, _bonus);

        _rewardToken.safeTransferFrom(address(msg.sender), address(this), requiredAmount);
        Farm newFarm = new Farm(address(factory), address(this));
        newFarm.transferOwnership(_manager);
        _rewardToken.safeApprove(address(newFarm), requiredAmount);
        newFarm.init(_rewardToken, requiredAmount, _lpToken, _blockReward, _startBlock, endBlock, _bonusEndBlock, _bonus);

        factory.registerFarm(address(newFarm));

        emit FarmCreated(_rewardToken, _amount, _lpToken, _blockReward, _startBlock, _bonusEndBlock, _bonus);
        return (address(newFarm));
    }
}
// import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
// import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
// import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
// import { IFarmFactory } from "contracts/interfaces/IFarmFactory.sol";
// import { Farm } from "contracts/farm/Farm.sol";
function createFarm(
    IERC20 _rewardToken,
    uint256 _amount,
    IERC20 _lpToken,
    uint256 _blockReward,
    uint256 _startBlock,
    uint256 _bonusEndBlock,
    uint256 _bonus,
    address _manager
)
    public
    onlyOwner
    returns (address)
{
    require(_startBlock > block.number, "START"); // ideally at least 24 hours more to give farmers time
    require(_bonus > 0, "BONUS");
    require(address(_rewardToken) != address(0), "TOKEN");
    require(_blockReward > 1000, "BR"); // minimum 1000 divisibility per block reward

    (uint256 endBlock, uint256 requiredAmount) = determineEndBlock(_amount, _blockReward, _startBlock, _bonusEndBlock, _bonus);

    _rewardToken.safeTransferFrom(address(msg.sender), address(this), requiredAmount);
    Farm newFarm = new Farm(address(factory), address(this));
    newFarm.transferOwnership(_manager);
    _rewardToken.safeApprove(address(newFarm), requiredAmount);
    newFarm.init(_rewardToken, requiredAmount, _lpToken, _blockReward, _startBlock, endBlock, _bonusEndBlock, _bonus);

    factory.registerFarm(address(newFarm));

    emit FarmCreated(_rewardToken, _amount, _lpToken, _blockReward, _startBlock, _bonusEndBlock, _bonus);
    return (address(newFarm));
}

/**
 * Creates a new Farm contract and registers it in the farm factory. Farming rewards are locked in the farm
 *
 * @param _rewardToken          Instance of reward token contract
 * @param _amount               Total sum of reward
 * @param _lpToken              Instance of LP token contract
 * @param _blockReward          Reward per block
 * @param _startBlock           Block number to start reward
 * @param _bonusEndBlock        Block number to end the bonus reward
 * @param _bonus                Bonus multipler which will be applied until bonus end block
 * @param _manager              Manager of the farm
 */