contract StabilizerPool is
    Initializable,
    LPTokenWrapper,
    IRewardDistributionRecipient
{
    event LogCountThreshold(uint256 countThreshold_);
    event LogBeforePeriodFinish(bool beforePeriodFinish_);
    event LogCountInSequence(bool CountInSequence_);
    event LogSetRewardAmount(uint256 rewardAmount_);

    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 ManualPoolStarted(uint256 startedAt);
    event LogSetDuration(uint256 duration);
    event LogSetPoolEnabled(bool poolEnabled);

    string public poolName;
    IERC20 public rewardToken;
    address public policy;
    uint256 public duration;
    bool public poolEnabled;

    uint256 public rewardAmount;
    uint256 public periodFinish;
    uint256 public rewardRate;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;
    uint256 public rewardDistributed;

    // The count of s hitting their target
    uint256 public count;

    // The threshold count on which to send rewards to the stabilizer pool
    uint256 public countThreshold;

    // Flag to enable or disable   sequence checker
    bool public countInSequence;

    // Flag to send reward before stabilizer pool period time finished
    bool public beforePeriodFinish;

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

    modifier checkStart() {
        require(poolEnabled == true, "Orchestrator hasn't started pool");
        _;
    }

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

    function initialize(
        string memory poolName_,
        address rewardToken_,
        address pairToken_,
        address policy_,
        uint256 rewardAmount_,
        uint256 duration_
    ) public initializer {
        poolName = poolName_;
        setStakeToken(pairToken_);
        rewardToken = IERC20(rewardToken_);
        policy = policy_;
        duration = duration_;
        poolEnabled = false;

        rewardAmount = rewardAmount_;
        count = 0;
        countThreshold = 20;
        countInSequence = true;
        beforePeriodFinish = false;
    }

    /**
     * @notice Upon succesive succesful s ( exchange price in target price ) the  count will increase. As the count increases if it
     * meets the set threshold. Then a precentage of debase tokens assigned to the policy contract will be transfered to the stabilizer pool.
     * With the added condition that the stabilizer pool has completed it's distribution period or a new flag is set to ovverride the time period.
     */
    function checkStabilizerAndGetReward(
        int256 supplyDelta_,
        int256 rebaseLag_,
        uint256 exchangeRate_,
        uint256 debasePolicyBalance
    ) external returns (uint256 rewardAmount_) {
        require(
            msg.sender == policy,
            "Only debase policy contract can call this"
        );

        if (supplyDelta_ == 0) {
            count = count.add(1);

            if (count >= countThreshold) {
                count = 0;
                if (
                    debasePolicyBalance >= rewardAmount &&
                    (beforePeriodFinish || now >= periodFinish)
                ) {
                    notifyRewardAmount(rewardAmount);
                    return rewardAmount;
                }
            }
        } else if (countInSequence && count != 0) {
            count = 0;
        }
        return 0;
    }

    function setRewardAmount(uint256 rewardAmount_) external onlyOwner {
        rewardAmount = rewardAmount_;
        emit LogSetRewardAmount(rewardAmount);
    }

    /**
     * @notice Function to enable or disable count should be in sequence
     */
    function setCountInSequence(bool countInSequence_) external onlyOwner {
        countInSequence = countInSequence_;
        count = 0;
        emit LogCountInSequence(!countInSequence);
    }

    /**
     * @notice Function to set the count threshold
     * @param countThreshold_ The new threshold
     */
    function setCountThreshold(uint256 countThreshold_) external onlyOwner {
        require(countThreshold_ >= 1);
        countThreshold = countThreshold_;
        count = 0;
        emit LogCountThreshold(countThreshold);
    }

    /**
     * @notice Function to allow reward distribution before previous rewards have been distributed
     * @param beforePeriodFinish_ Flag to toggle distribution
     */
    function setBeforePeriodFinish(bool beforePeriodFinish_)
        external
        onlyOwner
    {
        beforePeriodFinish = beforePeriodFinish_;
        emit LogBeforePeriodFinish(beforePeriodFinish);
    }

    /**
     * @notice Function to set reward drop period
     * @param duration_ New drop duration
     */
    function setDuration(uint256 duration_) external onlyOwner {
        require(duration >= 1);
        duration = duration_;
        emit LogSetDuration(duration);
    }

    /**
     * @notice Function enabled or disable pool staking,withdraw
     * @param poolEnabled_ Flag to toggle pool
     */
    function setPoolEnabled(bool poolEnabled_) external onlyOwner {
        poolEnabled = poolEnabled_;
        count = 0;
        emit LogSetPoolEnabled(poolEnabled);
    }

    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(10**18)
                    .div(totalSupply())
            );
    }

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

    // stake visibility is public as overriding LPTokenWrapper's stake() function
    function stake(uint256 amount)
        public
        override
        updateReward(msg.sender)
        checkStart
    {
        require(amount > 0, "Cannot stake 0");
        super.stake(amount);
        emit Staked(msg.sender, amount);
    }

    function withdraw(uint256 amount)
        public
        override
        updateReward(msg.sender)
        checkStart
    {
        require(amount > 0, "Cannot withdraw 0");
        super.withdraw(amount);
        emit Withdrawn(msg.sender, amount);
    }

    function exit() external {
        withdraw(balanceOf(msg.sender));
        getReward();
    }

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

    function notifyRewardAmount(uint256 reward)
        internal
        override
        updateReward(address(0))
    {
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(duration);
        } else {
            uint256 remaining = periodFinish.sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = reward.add(leftover).div(duration);
        }
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(duration);
        emit RewardAdded(reward);
    }
}
function setCountInSequence(bool countInSequence_) external onlyOwner {
    countInSequence = countInSequence_;
    count = 0;
    emit LogCountInSequence(!countInSequence);
}

/**
 * @notice Function to enable or disable count should be in sequence
 */