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

    IERC20 public strn;// = IERC20(0x6a61255575eeF1Fd45CFA406d959180F463638fc);

    address public treasury;

    // uint256 public START_DELAY = 2 minutes;

    //Daily rate adjustment, rate increase for first month then decreases daily until all tokens distributed
    uint256 public constant DURATION = 1 days;//5 minutes;
    uint256 public constant RATE_INCREASE_DURATION = 30 days;//150 minutes;

    uint256 public constant MAX_REWARD = 8 * 10**6 * 10**18;

    uint256 public totalRewardMinted = 0;

    uint256 public initreward = 218193723 * 10**14; // 21819.3723 daily reward strain token
    uint256 public inittreasuryreward = 218193723 * 10**13; // 2181.93723 daily strain token goes to treasury
    uint256 public starttime = 1603939800; // 2020-09-20 00:00:00 (UTC +00:00)
    uint256 public periodFinish = 0;
    uint256 public rateIncreasePeriodFinish = 0;
    uint256 public rewardRate = 0;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;

    bool public initialized = false;

    LPTokenWrapper[] pools;

    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;


    event RewardAdded(uint256 reward);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);
    event PoolAdded(address pool, uint256 poolSize);

    constructor(address _strn) public {
        strn = IERC20(_strn);
    }

    function initialize(address _treasury) public onlyOwner{
        treasury = _treasury;
        strn.mint(address(this), initreward);
        strn.mint(treasury, inittreasuryreward);
        totalRewardMinted = totalRewardMinted.add(initreward).add(inittreasuryreward);
        // starttime = block.timestamp.add(START_DELAY);
        rewardRate = initreward.div(DURATION);
        lastUpdateTime = starttime;
        periodFinish = starttime.add(DURATION);
        rateIncreasePeriodFinish = starttime.add(RATE_INCREASE_DURATION);
    }

    function addPool(address pool) public onlyOwner{
        LPTokenWrapper lptoken = new LPTokenWrapper(pool);
        pools.push(lptoken);
        emit PoolAdded(pool, pools.length);
    }

    function totalSupply() public view returns (uint256) {
        uint256 total_supply = 0;
        for (uint i = 0; i < pools.length; i++) {
            total_supply = total_supply.add(pools[i].totalSupply());
        }
        return total_supply;
    }

    function balanceOf(address account) public view returns (uint256) {
        uint256 balance = 0;
        for (uint i = 0; i < pools.length; i++) {
            balance = balance.add(pools[i].balanceOf(account));
        }
        return balance;
    }

    function lastTimeRewardApplicable() public view returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }

    function rewardPerToken() public view returns (uint256) {
        if (totalSupply() == 0) {
            return rewardPerTokenStored;
        }
        return
            rewardPerTokenStored.add(
                lastTimeRewardApplicable()
                    .sub(lastUpdateTime)
                    .mul(rewardRate)
                    .mul(1e18)
                    .div(totalSupply())
            );
    }

    function earned(address account) public view returns (uint256) {
        return
            balanceOf(account)
                .mul(rewardPerToken().sub(userRewardPerTokenPaid[account]))
                .div(1e18)
                .add(rewards[account]);
    }

    function stake(uint256 amount, uint256 poolID) public checkStart {
        require(amount > 0, "Cannot stake 0");
        updateReward(msg.sender);
        pools[poolID].stake(msg.sender, amount);
        pools[poolID].uni_lp().safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }

    function withdraw(uint256 amount, uint256 poolID) public checkStart {
        require(amount > 0, "Cannot withdraw 0");
        updateReward(msg.sender);
        pools[poolID].withdraw(msg.sender, amount);
        pools[poolID].uni_lp().safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function exit(uint256 poolID) external {
        withdraw(pools[poolID].balanceOf(msg.sender), poolID);
        getReward();
    }

    function getReward() public checkStart {
        updateReward(msg.sender);
        uint256 reward = earned(msg.sender);
        if (reward > 0) {
            rewards[msg.sender] = 0;
            strn.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }

    function updateReward(address account) public {
        updateRewardRate();
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
    }

    //Reward increases during first month, then decreases
    function updateRewardRate() public {
        uint256 reward = 0;
        uint256 treasuryReward = 0;

        if(totalRewardMinted >= MAX_REWARD){
            // initreward = 0;
            // inittreasuryreward = 0;
            // rewardRate = 0;
            return;
        }

        while (block.timestamp >= periodFinish) {

            if(block.timestamp < rateIncreasePeriodFinish){
                initreward = initreward.mul(106).div(100);
                inittreasuryreward = inittreasuryreward.mul(106).div(100);
            }
            else{
                initreward = initreward.mul(98).div(100);
                inittreasuryreward = inittreasuryreward.mul(98).div(100);
            }

            //If exceeded max rewards, distribute the remaining rewards
            uint256 totalrewardstomint = totalRewardMinted.add(initreward).add(inittreasuryreward);
            if(totalrewardstomint >= MAX_REWARD){
                uint256 rewardsdiff = MAX_REWARD.sub(totalRewardMinted);
                initreward = rewardsdiff.mul(90).div(100);
                inittreasuryreward = rewardsdiff.sub(initreward);
            }

            reward = reward.add(initreward);
            treasuryReward = treasuryReward.add(inittreasuryreward);
            totalRewardMinted = totalRewardMinted.add(initreward).add(inittreasuryreward);

            rewardRate = initreward.div(DURATION);
            periodFinish = periodFinish.add(DURATION);

            if(totalRewardMinted >= MAX_REWARD){
                break;
            }
        }
        strn.mint(address(this), reward);
        strn.mint(treasury, treasuryReward);
        emit RewardAdded(reward);
    }

    modifier checkStart(){
        require(block.timestamp >= starttime,"not start");
        _;
    }

    // This function allows governance to take unsupported tokens out of the
    // contract, since this one exists longer than the other pools.
    // This is in an effort to make someone whole, should they seriously
    // mess up. There is no guarantee governance will vote to return these.
    // It also allows for removal of airdropped tokens.
    function rescueTokens(IERC20 _token, uint256 amount, address to)
        external
    {
        // only gov
        require(msg.sender == owner(), "!governance");
        // cant take staked asset
        for (uint i = 0; i < pools.length; i++) {
            IERC20 lp = pools[i].uni_lp();
            require(_token != lp, "uni_lp");
        }
        // cant take reward asset
        require(_token != strn, "strn");

        // transfer to
        _token.safeTransfer(to, amount);
    }
}
function rescueTokens(IERC20 _token, uint256 amount, address to)
    external
{
    // only gov
    require(msg.sender == owner(), "!governance");
    // cant take staked asset
    for (uint i = 0; i < pools.length; i++) {
        IERC20 lp = pools[i].uni_lp();
        require(_token != lp, "uni_lp");
    }
    // cant take reward asset
    require(_token != strn, "strn");

    // transfer to
    _token.safeTransfer(to, amount);
}

// This function allows governance to take unsupported tokens out of the
// contract, since this one exists longer than the other pools.
// This is in an effort to make someone whole, should they seriously
// mess up. There is no guarantee governance will vote to return these.
// It also allows for removal of airdropped tokens.