contract FriesDAOStakingPool {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // governance
    address public operator;

    // Info of each user.
    struct UserInfo {
        uint256 amount; // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 token; // Address of LP token contract.
        uint256 allocPoint; // How many allocation points assigned to this pool. Kchups to distribute in the pool.
        uint256 lastRewardTime; // Last time that Kchups distribution occurred.
        uint256 accKchupPerShare; // Accumulated Kchups per share, times 1e18. See below.
        bool isStarted; // if lastRewardTime has passed
    }

    IERC20 public kchup;

    // Info of each pool.
    PoolInfo[] public poolInfo;

    // Info of each user that stakes LP tokens.
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;

    // Total allocation points. Must be the sum of all allocation points in all pools.
    uint256 public totalAllocPoint = 0;

    // The time when KCHUP mining starts.
    uint256 public poolStartTime;

    uint256[] public epochTotalRewards = [
        49521739.29 ether,
        45888889.29 ether,
        42528503.04 ether,
        39420145.76 ether,
        36544915.27 ether,
        33885327.07 ether,
        31425207.99 ether,
        29149597.84 ether,
        27044658.44 ether,
        25097589.51 ether,
        23296550.74 ether,
        21630589.88 ether,
        20089576.09 ether,
        18664138.33 ether,
        17345608.40 ether,
        16125968.22 ether,
        14997801.05 ether,
        13954246.41 ether,
        12988958.38 ether,
        12096066.95 ether,
        11270142.37 ether,
        10506162.14 ether,
        9799480.43 ether,
        9145799.84 ether,
        8541145.30 ether,
        7981839.85 ether,
        7464482.31 ether,
        6985926.58 ether,
        6543262.53 ether,
        6133798.29 ether,
        5755043.86 ether,
        5404696.02 ether,
        5080624.27 ether,
        4780857.89 ether,
        4503574.00 ether,
        4247086.39 ether
    ];

    // Time when each epoch ends.
    uint256[37] public epochEndTimes;

    // Reward per second for each of 2 epochs (last item is equal to 0 - for sanity).
    uint256[37] public epochKchupPerSecond;

    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event RewardPaid(address indexed user, uint256 amount);

    constructor(address _kchup, uint256 _poolStartTime) public {
        require(block.timestamp < _poolStartTime, "late");
        if (_kchup != address(0)) kchup = IERC20(_kchup);

        poolStartTime = _poolStartTime;


        epochEndTimes[0] = poolStartTime + 30 days;
        epochEndTimes[1] = poolStartTime + 60 days;
        epochEndTimes[2] = poolStartTime + 90 days;
        epochEndTimes[3] = poolStartTime + 120 days;
        epochEndTimes[4] = poolStartTime + 150 days;
        epochEndTimes[5] = poolStartTime + 180 days;
        epochEndTimes[6] = poolStartTime + 210 days;
        epochEndTimes[7] = poolStartTime + 240 days;
        epochEndTimes[8] = poolStartTime + 270 days;
        epochEndTimes[9] = poolStartTime + 300 days;
        epochEndTimes[10] = poolStartTime + 330 days;
        epochEndTimes[11] = poolStartTime + 360 days;
        epochEndTimes[12] = poolStartTime + 390 days;
        epochEndTimes[13] = poolStartTime + 420 days;
        epochEndTimes[14] = poolStartTime + 450 days;
        epochEndTimes[15] = poolStartTime + 480 days;
        epochEndTimes[16] = poolStartTime + 510 days;
        epochEndTimes[17] = poolStartTime + 540 days;
        epochEndTimes[18] = poolStartTime + 570 days;
        epochEndTimes[19] = poolStartTime + 600 days;
        epochEndTimes[20] = poolStartTime + 630 days;
        epochEndTimes[21] = poolStartTime + 660 days;
        epochEndTimes[22] = poolStartTime + 690 days;
        epochEndTimes[23] = poolStartTime + 720 days;
        epochEndTimes[24] = poolStartTime + 750 days;
        epochEndTimes[25] = poolStartTime + 780 days;
        epochEndTimes[26] = poolStartTime + 810 days;
        epochEndTimes[27] = poolStartTime + 840 days;
        epochEndTimes[28] = poolStartTime + 870 days;
        epochEndTimes[29] = poolStartTime + 900 days;
        epochEndTimes[30] = poolStartTime + 930 days;
        epochEndTimes[31] = poolStartTime + 960 days;
        epochEndTimes[32] = poolStartTime + 990 days;
        epochEndTimes[33] = poolStartTime + 1020 days;
        epochEndTimes[34] = poolStartTime + 1050 days;
        epochEndTimes[35] = poolStartTime + 1080 days;


        epochKchupPerSecond[0] = epochTotalRewards[0].div(30 days);
        epochKchupPerSecond[1] = epochTotalRewards[1].div(30 days);
        epochKchupPerSecond[2] = epochTotalRewards[2].div(30 days);
        epochKchupPerSecond[3] = epochTotalRewards[3].div(30 days);
        epochKchupPerSecond[4] = epochTotalRewards[4].div(30 days);
        epochKchupPerSecond[5] = epochTotalRewards[5].div(30 days);
        epochKchupPerSecond[6] = epochTotalRewards[6].div(30 days);
        epochKchupPerSecond[7] = epochTotalRewards[7].div(30 days);
        epochKchupPerSecond[8] = epochTotalRewards[8].div(30 days);
        epochKchupPerSecond[9] = epochTotalRewards[9].div(30 days);
        epochKchupPerSecond[10] = epochTotalRewards[10].div(30 days);
        epochKchupPerSecond[11] = epochTotalRewards[11].div(30 days);
        epochKchupPerSecond[12] = epochTotalRewards[12].div(30 days);
        epochKchupPerSecond[13] = epochTotalRewards[13].div(30 days);
        epochKchupPerSecond[14] = epochTotalRewards[14].div(30 days);
        epochKchupPerSecond[15] = epochTotalRewards[15].div(30 days);
        epochKchupPerSecond[16] = epochTotalRewards[16].div(30 days);
        epochKchupPerSecond[17] = epochTotalRewards[17].div(30 days);
        epochKchupPerSecond[18] = epochTotalRewards[18].div(30 days);
        epochKchupPerSecond[19] = epochTotalRewards[19].div(30 days);
        epochKchupPerSecond[20] = epochTotalRewards[20].div(30 days);
        epochKchupPerSecond[21] = epochTotalRewards[21].div(30 days);
        epochKchupPerSecond[22] = epochTotalRewards[22].div(30 days);
        epochKchupPerSecond[23] = epochTotalRewards[23].div(30 days);
        epochKchupPerSecond[24] = epochTotalRewards[24].div(30 days);
        epochKchupPerSecond[25] = epochTotalRewards[25].div(30 days);
        epochKchupPerSecond[26] = epochTotalRewards[26].div(30 days);
        epochKchupPerSecond[27] = epochTotalRewards[27].div(30 days);
        epochKchupPerSecond[28] = epochTotalRewards[28].div(30 days);
        epochKchupPerSecond[29] = epochTotalRewards[29].div(30 days);
        epochKchupPerSecond[30] = epochTotalRewards[30].div(30 days);
        epochKchupPerSecond[31] = epochTotalRewards[31].div(30 days);
        epochKchupPerSecond[32] = epochTotalRewards[32].div(30 days);
        epochKchupPerSecond[33] = epochTotalRewards[33].div(30 days);
        epochKchupPerSecond[34] = epochTotalRewards[34].div(30 days);
        epochKchupPerSecond[35] = epochTotalRewards[35].div(30 days);

        

        epochKchupPerSecond[36] = 0;
        operator = msg.sender;
    }

    modifier onlyOperator() {
        require(operator == msg.sender, "FriesDAOStakingPool: caller is not the operator");
        _;
    }

    function checkPoolDuplicate(IERC20 _token) internal view {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            require(poolInfo[pid].token != _token, "FriesDAOStakingPool: existing pool?");
        }
    }

    // Add a new token to the pool. Can only be called by the owner.
    function add(
        uint256 _allocPoint,
        IERC20 _token,
        bool _withUpdate,
        uint256 _lastRewardTime
    ) public onlyOperator {
        checkPoolDuplicate(_token);
        if (_withUpdate) {
            massUpdatePools();
        }
        if (block.timestamp < poolStartTime) {
            // chef is sleeping
            if (_lastRewardTime == 0) {
                _lastRewardTime = poolStartTime;
            } else {
                if (_lastRewardTime < poolStartTime) {
                    _lastRewardTime = poolStartTime;
                }
            }
        } else {
            // chef is cooking
            if (_lastRewardTime == 0 || _lastRewardTime < block.timestamp) {
                _lastRewardTime = block.timestamp;
            }
        }
        bool _isStarted = (_lastRewardTime <= poolStartTime) || (_lastRewardTime <= block.timestamp);
        poolInfo.push(PoolInfo({token: _token, allocPoint: _allocPoint, lastRewardTime: _lastRewardTime, accKchupPerShare: 0, isStarted: _isStarted}));
        if (_isStarted) {
            totalAllocPoint = totalAllocPoint.add(_allocPoint);
        }
    }

    // Update the given pool's KCHUP allocation point. Can only be called by the owner.
    function set(uint256 _pid, uint256 _allocPoint) public onlyOperator {
        massUpdatePools();
        PoolInfo storage pool = poolInfo[_pid];
        if (pool.isStarted) {
            totalAllocPoint = totalAllocPoint.sub(pool.allocPoint).add(_allocPoint);
        }
        pool.allocPoint = _allocPoint;
    }

    // Return accumulate rewards over the given _fromTime to _toTime.
    function getGeneratedReward(uint256 _fromTime, uint256 _toTime) public view returns (uint256) {
        for (uint8 epochId = 36; epochId >= 1; --epochId) {
            if (_toTime >= epochEndTimes[epochId - 1]) {
                if (_fromTime >= epochEndTimes[epochId - 1]) {
                    return _toTime.sub(_fromTime).mul(epochKchupPerSecond[epochId]);
                }

                uint256 _generatedReward = _toTime.sub(epochEndTimes[epochId - 1]).mul(epochKchupPerSecond[epochId]);
                if (epochId == 1) {
                    return _generatedReward.add(epochEndTimes[0].sub(_fromTime).mul(epochKchupPerSecond[0]));
                }
                for (epochId = epochId - 1; epochId >= 1; --epochId) {
                    if (_fromTime >= epochEndTimes[epochId - 1]) {
                        return _generatedReward.add(epochEndTimes[epochId].sub(_fromTime).mul(epochKchupPerSecond[epochId]));
                    }
                    _generatedReward = _generatedReward.add(epochEndTimes[epochId].sub(epochEndTimes[epochId - 1]).mul(epochKchupPerSecond[epochId]));
                }
                return _generatedReward.add(epochEndTimes[0].sub(_fromTime).mul(epochKchupPerSecond[0]));
            }
        }
        return _toTime.sub(_fromTime).mul(epochKchupPerSecond[0]);
    }

    // View function to see pending KCHUPs on frontend.
    function pendingKCHUP(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accKchupPerShare = pool.accKchupPerShare;
        uint256 tokenSupply = pool.token.balanceOf(address(this));
        if (block.timestamp > pool.lastRewardTime && tokenSupply != 0) {
            uint256 _generatedReward = getGeneratedReward(pool.lastRewardTime, block.timestamp);
            uint256 _kchupReward = _generatedReward.mul(pool.allocPoint).div(totalAllocPoint);
            accKchupPerShare = accKchupPerShare.add(_kchupReward.mul(1e18).div(tokenSupply));
        }
        return user.amount.mul(accKchupPerShare).div(1e18).sub(user.rewardDebt);
    }

    // Update reward variables for all pools. Expensive!
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        if (block.timestamp <= pool.lastRewardTime) {
            return;
        }
        uint256 tokenSupply = pool.token.balanceOf(address(this));
        if (tokenSupply == 0) {
            pool.lastRewardTime = block.timestamp;
            return;
        }
        if (!pool.isStarted) {
            pool.isStarted = true;
            totalAllocPoint = totalAllocPoint.add(pool.allocPoint);
        }
        if (totalAllocPoint > 0) {
            uint256 _generatedReward = getGeneratedReward(pool.lastRewardTime, block.timestamp);
            uint256 _kchupReward = _generatedReward.mul(pool.allocPoint).div(totalAllocPoint);
            pool.accKchupPerShare = pool.accKchupPerShare.add(_kchupReward.mul(1e18).div(tokenSupply));
        }
        pool.lastRewardTime = block.timestamp;
    }

    // Deposit LP tokens.
    function deposit(uint256 _pid, uint256 _amount) public {
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 _pending = user.amount.mul(pool.accKchupPerShare).div(1e18).sub(user.rewardDebt);
            if (_pending > 0) {
                safeKchupTransfer(_sender, _pending);
                emit RewardPaid(_sender, _pending);
            }
        }
        if (_amount > 0) {
            pool.token.safeTransferFrom(_sender, address(this), _amount);
            user.amount = user.amount.add(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accKchupPerShare).div(1e18);
        emit Deposit(_sender, _pid, _amount);
    }

    // Withdraw LP tokens.
    function withdraw(uint256 _pid, uint256 _amount) public {
        address _sender = msg.sender;
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 _pending = user.amount.mul(pool.accKchupPerShare).div(1e18).sub(user.rewardDebt);
        if (_pending > 0) {
            safeKchupTransfer(_sender, _pending);
            emit RewardPaid(_sender, _pending);
        }
        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.token.safeTransfer(_sender, _amount);
        }
        user.rewardDebt = user.amount.mul(pool.accKchupPerShare).div(1e18);
        emit Withdraw(_sender, _pid, _amount);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 _amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;
        pool.token.safeTransfer(msg.sender, _amount);
        emit EmergencyWithdraw(msg.sender, _pid, _amount);
    }

    // Safe kchup transfer function, just in case if rounding error causes pool to not have enough Kchups.
    function safeKchupTransfer(address _to, uint256 _amount) internal {
        uint256 _kchupBal = kchup.balanceOf(address(this));
        if (_kchupBal > 0) {
            if (_amount > _kchupBal) {
                kchup.safeTransfer(_to, _kchupBal);
            } else {
                kchup.safeTransfer(_to, _amount);
            }
        }
    }

    function setOperator(address _operator) external onlyOperator {
        operator = _operator;
    }

    function governanceRecoverUnsupported(
        IERC20 _token,
        uint256 amount,
        address to
    ) external onlyOperator {
        if (block.timestamp < epochEndTimes[1] + 30 days) {
            // do not allow to drain token if less than 30 days after farming
            require(_token != kchup, "!kchup");
            uint256 length = poolInfo.length;
            for (uint256 pid = 0; pid < length; ++pid) {
                PoolInfo storage pool = poolInfo[pid];
                require(_token != pool.token, "!pool.token");
            }
        }
        _token.safeTransfer(to, amount);
    }
}
// Note that this pool has no minter key of KCHUP (rewards).
// Instead, the governance will call KCHUP distributeReward method and send reward to this pool at the beginning.
function deposit(uint256 _pid, uint256 _amount) public {
    address _sender = msg.sender;
    PoolInfo storage pool = poolInfo[_pid];
    UserInfo storage user = userInfo[_pid][_sender];
    updatePool(_pid);
    if (user.amount > 0) {
        uint256 _pending = user.amount.mul(pool.accKchupPerShare).div(1e18).sub(user.rewardDebt);
        if (_pending > 0) {
            safeKchupTransfer(_sender, _pending);
            emit RewardPaid(_sender, _pending);
        }
    }
    if (_amount > 0) {
        pool.token.safeTransferFrom(_sender, address(this), _amount);
        user.amount = user.amount.add(_amount);
    }
    user.rewardDebt = user.amount.mul(pool.accKchupPerShare).div(1e18);
    emit Deposit(_sender, _pid, _amount);
}

// Deposit LP tokens.