contract FarmGenerator01 is Ownable {
    using SafeMath for uint256;
    
    FarmFactory public factory;
    
    struct FarmParameters {
        uint256 bonusBlocks;
        uint256 totalBonusReward;
        uint256 numBlocks;
        uint256 endBlock;
        uint256 requiredAmount;
    }
    
    constructor(FarmFactory _factory) public {
        factory = _factory;
    }
    
    /**
     * @notice Determine the endBlock based on inputs. Used on the front end to show the exact settings the Farm contract will be deployed with
     */
    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);
    }
    
    /**
     * @notice 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
     */
    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);
    }
    
    /**
     * @notice Creates a new Farm contract and registers it in the 
     * .sol. All farming rewards are locked in the Farm Contract
     */
    function createFarm (IERC20 _rewardToken, uint256 _amount, IERC20 _lpToken,IUniFactory _swapFactory, uint256 _blockReward, uint256 _startBlock, uint256 _bonusEndBlock, uint256 _bonus) 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
        IUniFactory swapFactory = _swapFactory;
        // ensure this pair is on swapFactory by querying the factory
        IUniswapV2Pair lpair = IUniswapV2Pair(address(_lpToken));
        address factoryPairAddress = swapFactory.getPair(lpair.token0(), lpair.token1());
        require(factoryPairAddress == address(_lpToken), 'This pair is not on _swapFactory exchange');
        
        FarmParameters memory params;
        (params.endBlock, params.requiredAmount) = determineEndBlock(_amount, _blockReward, _startBlock, _bonusEndBlock, _bonus);
        
        TransferHelper.safeTransferFrom(address(_rewardToken), address(msg.sender), address(this), params.requiredAmount);
        Farm01 newFarm = new Farm01(address(factory), address(this));
        TransferHelper.safeApprove(address(_rewardToken), address(newFarm), params.requiredAmount);
        newFarm.init(_rewardToken, params.requiredAmount, _lpToken, _blockReward, _startBlock, params.endBlock, _bonusEndBlock, _bonus);
        
        factory.registerFarm(address(newFarm));
        return (address(newFarm));
    }
    
}
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);
}

/**
 * @notice Determine the endBlock based on inputs. Used on the front end to show the exact settings the Farm contract will be deployed with
 */