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

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

    struct UserInfo {
        uint256 amount;     // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt.
    }

    struct FarmInfo {
        IERC20 lpToken;
        IERC20 rewardToken;
        uint256 startBlock;
        uint256 blockReward;
        uint256 bonusEndBlock;
        uint256 bonus;
        uint256 endBlock;
        uint256 lastRewardBlock;  // Last block number that reward distribution occurs.
        uint256 accRewardPerShare; // Accumulated Rewards per share, times 1e12
        uint256 farmableSupply; // set in init, total amount of tokens farmable
        uint256 numFarmers;
    }

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

    // Useful for back-end systems to know how to read the contract (ABI) as we plan to launch multiple farm types
    uint256 public farmType = 1;

    IFarmFactory public factory;
    address public farmGenerator;

    FarmInfo public farmInfo;

    // Information on each user than stakes LP tokens
    mapping (address => UserInfo) public userInfo;

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

    event Deposit(address indexed user, uint256 amount);
    event Withdraw(address indexed user, uint256 amount);
    event WithdrawWithoutReward(address indexed user, uint256 amount);

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

    /**
     * Store factory and generator instance.
     *
     * @param _factory              Instance of Farm Factory
     * @param _farmGenerator        Instance of Farm Generator
     */
    constructor(address _factory, address _farmGenerator) public {
        factory = IFarmFactory(_factory);
        farmGenerator = _farmGenerator;
    }

    /* ============ Public/External functions ============ */

    /**
     * Initialize the farming contract. This is called only once upon farm creation and the FarmGenerator
     * ensures the farm has the correct paramaters
     *
     * @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
     */
    function init(
        IERC20 _rewardToken,
        uint256 _amount,
        IERC20 _lpToken,
        uint256 _blockReward,
        uint256 _startBlock,
        uint256 _endBlock,
        uint256 _bonusEndBlock,
        uint256 _bonus
    ) external {
        require(msg.sender == address(farmGenerator), "FORBIDDEN");

        _rewardToken.safeTransferFrom(msg.sender, address(this), _amount);
        farmInfo.rewardToken = _rewardToken;

        farmInfo.startBlock = _startBlock;
        farmInfo.blockReward = _blockReward;
        farmInfo.bonusEndBlock = _bonusEndBlock;
        farmInfo.bonus = _bonus;

        uint256 lastRewardBlock = block.number > _startBlock ? block.number : _startBlock;
        farmInfo.lpToken = _lpToken;
        farmInfo.lastRewardBlock = lastRewardBlock;
        farmInfo.accRewardPerShare = 0;

        farmInfo.endBlock = _endBlock;
        farmInfo.farmableSupply = _amount;
    }

    /**
     * Updates pool information to be up to date to the current block
     */
    function updatePool() public {
        if (block.number <= farmInfo.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = farmInfo.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            farmInfo.lastRewardBlock = block.number < farmInfo.endBlock ? block.number : farmInfo.endBlock;
            return;
        }
        uint256 multiplier = getMultiplier(farmInfo.lastRewardBlock, block.number);
        uint256 tokenReward = multiplier.mul(farmInfo.blockReward);
        farmInfo.accRewardPerShare = farmInfo.accRewardPerShare.add(tokenReward.mul(1e12).div(lpSupply));
        farmInfo.lastRewardBlock = block.number < farmInfo.endBlock ? block.number : farmInfo.endBlock;
    }

    /**
     * Deposit LP token function for msg.sender
     *
     * @param _amount               the total deposit amount
     */
    function deposit(uint256 _amount) external {
        UserInfo storage user = userInfo[msg.sender];
        updatePool();
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
            _safeRewardTransfer(msg.sender, pending);
        }
        if (user.amount == 0 && _amount > 0) {
            factory.userEnteredFarm(msg.sender);
            farmInfo.numFarmers++;
        }
        farmInfo.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
        emit Deposit(msg.sender, _amount);
    }

    /**
     * Withdraw LP token function for msg.sender
     *
     * @param                       _amount the total withdrawable amount
     */
    function withdraw(uint256 _amount) external {
        UserInfo storage user = userInfo[msg.sender];
        require(user.amount >= _amount, "INSUFFICIENT");
        updatePool();
        if (user.amount == _amount && _amount > 0) {
            factory.userLeftFarm(msg.sender);
            farmInfo.numFarmers--;
        }
        uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
        _safeRewardTransfer(msg.sender, pending);
        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
        farmInfo.lpToken.safeTransfer(address(msg.sender), _amount);
        emit Withdraw(msg.sender, _amount);
    }

    /**
     * Function to withdraw LP tokens and forego harvest rewards. Important to protect users LP tokens
     */
    function withdrawWithoutReward() external {
        UserInfo storage user = userInfo[msg.sender];
        farmInfo.lpToken.safeTransfer(address(msg.sender), user.amount);
        emit WithdrawWithoutReward(msg.sender, user.amount);
        if (user.amount > 0) {
            factory.userLeftFarm(msg.sender);
            farmInfo.numFarmers--;
        }
        user.amount = 0;
        user.rewardDebt = 0;
    }

    /**
     * Withdraw tokens emergency.
     *
     * @param _token                Token contract address
     * @param _to                   Address where the token withdraw to
     * @param _amount               Amount of tokens to withdraw
     */
    function emergencyWithdraw(address _token, address _to, uint256 _amount) external onlyOwner {
        IERC20 erc20Token = IERC20(_token);
        require(erc20Token.balanceOf(address(this)) > 0, "Insufficient balane");

        uint256 amountToWithdraw = _amount;
        if (_amount == 0) {
            amountToWithdraw = erc20Token.balanceOf(address(this));
        }
        erc20Token.safeTransfer(_to, amountToWithdraw);
    }

    /* ============ View functions ============ */

    /**
     * Get the reward multiplier over the given _from_block until _to block
     *
     * @param _fromBlock            the start of the period to measure rewards for
     * @param _to                   the end of the period to measure rewards for
     *
     * @return                      The weighted multiplier for the given period
     */
    function getMultiplier(uint256 _fromBlock, uint256 _to) public view returns (uint256) {
        uint256 _from = _fromBlock >= farmInfo.startBlock ? _fromBlock : farmInfo.startBlock;
        uint256 to = farmInfo.endBlock > _to ? _to : farmInfo.endBlock;
        if (to <= farmInfo.bonusEndBlock) {
            return to.sub(_from).mul(farmInfo.bonus);
        } else if (_from >= farmInfo.bonusEndBlock) {
            return to.sub(_from);
        } else {
            return farmInfo.bonusEndBlock.sub(_from).mul(farmInfo.bonus).add(
                to.sub(farmInfo.bonusEndBlock)
            );
        }
    }

    /**
     * Function to see accumulated balance of reward token for specified user
     *
     * @param _user                 the user for whom unclaimed tokens will be shown
     *
     * @return                      total amount of withdrawable reward tokens
     */
    function pendingReward(address _user) external view returns (uint256) {
        UserInfo storage user = userInfo[_user];
        uint256 accRewardPerShare = farmInfo.accRewardPerShare;
        uint256 lpSupply = farmInfo.lpToken.balanceOf(address(this));
        if (block.number > farmInfo.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier = getMultiplier(farmInfo.lastRewardBlock, block.number);
            uint256 tokenReward = multiplier.mul(farmInfo.blockReward);
            accRewardPerShare = accRewardPerShare.add(tokenReward.mul(1e12).div(lpSupply));
        }
        return user.amount.mul(accRewardPerShare).div(1e12).sub(user.rewardDebt);
    }

    /* ============ Internal functions ============ */

    /**
     * Safe reward transfer function, just in case a rounding error causes pool to not have enough reward tokens
     *
     * @param _to                   the user address to transfer tokens to
     * @param _amount               the total amount of tokens to transfer
     */
    function _safeRewardTransfer(address _to, uint256 _amount) internal {
        uint256 rewardBal = farmInfo.rewardToken.balanceOf(address(this));
        if (_amount > rewardBal) {
            farmInfo.rewardToken.transfer(_to, rewardBal);
        } else {
            farmInfo.rewardToken.transfer(_to, _amount);
        }
    }
}
// Dependency file: contracts/farm/Farm.sol
// pragma solidity 0.6.10;
// 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 { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
// import { IFarmFactory } from "contracts/interfaces/IFarmFactory.sol";
function deposit(uint256 _amount) external {
    UserInfo storage user = userInfo[msg.sender];
    updatePool();
    if (user.amount > 0) {
        uint256 pending = user.amount.mul(farmInfo.accRewardPerShare).div(1e12).sub(user.rewardDebt);
        _safeRewardTransfer(msg.sender, pending);
    }
    if (user.amount == 0 && _amount > 0) {
        factory.userEnteredFarm(msg.sender);
        farmInfo.numFarmers++;
    }
    farmInfo.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
    user.amount = user.amount.add(_amount);
    user.rewardDebt = user.amount.mul(farmInfo.accRewardPerShare).div(1e12);
    emit Deposit(msg.sender, _amount);
}

/**
 * Deposit LP token function for msg.sender
 *
 * @param _amount               the total deposit amount
 */