contract FecoreDynamicStake2 is
    StakeTokenWrapper(0xfb8D6DB914A636a4437e1056e1780Ed3Ae3AAD5A),
    IGovernanceAddressRecipient
{
    IERC20 public FECORE = IERC20(0x6971fD17a3f82a7b9C957C82c6F4a5C8723Ba6C9);

    uint256 public constant DURATION = 60 days;

    uint256 public initReward = 0;
    uint256 public startTime = 0;
    uint256 public periodFinish = 0;
    uint256 public rewardRate = 0;
    uint256 public lastUpdateTime;
    bool public stakeable = false;
    uint256 public rewardPerTokenStored;
    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;
    address public FEE_ADDRESS;

    event DepositStake(uint256 reward);
    event StartStaking(uint256 time);
    event StopStaking(uint256 time);
    event Staked(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, uint256 amount);
    event RewardPaid(address indexed user, uint256 reward);

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

    modifier checkStart() {
        require(initReward > 0, "No reward to stake.");
        require(stakeable, "Staking is not started.");
        _;
    }

    constructor() public {
        GovernanceAddress = _msgSender();
        FEE_ADDRESS = _msgSender();
    }

    function setFeeAddress(address _feeAddress) public onlyGovernanceAddress {
        FEE_ADDRESS = _feeAddress;
    }

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

    function remainingReward() public view returns (uint256) {
        return FECORE.balanceOf(address(this));
    }

    function stop() public onlyGovernanceAddress {
        require(stakeable, "Staking is not started.");
        FECORE.safeTransfer(
            address(0x489B689850999F751760a38d03693Bd979C4A690),
            remainingReward()
        );
        stakeable = false;
        initReward = 0;
        rewardRate = 0;
        emit StopStaking(block.timestamp);
    }

    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 start() public onlyGovernanceAddress {
        require(!stakeable, "Staking is started.");
        require(initReward > 0, "Cannot start. Require initReward");
        periodFinish = block.timestamp.add(DURATION);
        stakeable = true;
        startTime = block.timestamp;
        emit StartStaking(block.timestamp);
    }

    function depositReward(uint256 amount) public onlyGovernanceAddress {
        require(!stakeable, "Staking is started.");
        require(amount > 0, "Cannot deposit 0");
        FECORE.safeTransferFrom(_msgSender(), address(this), amount);
        initReward = amount;
        rewardRate = initReward.div(DURATION);
        emit DepositStake(amount);
    }

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

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

    function exitStake() external {
        withdraw(balanceOf(_msgSender()));
        getReward();
    }

    uint256 public FEE_RATE = 1;
    uint256 public constant PERCENTS_DIVIDER = 1000;

    function getReward() public updateReward(_msgSender()) checkStart {
        uint256 reward = earned(_msgSender());
        if (reward > 0) {
            uint256 fee = reward.div(PERCENTS_DIVIDER).mul(FEE_RATE);
            rewards[_msgSender()] = 0;
            FECORE.safeTransfer(FEE_ADDRESS, fee);
            FECORE.safeTransfer(_msgSender(), reward.sub(fee));
            emit RewardPaid(_msgSender(), reward);
        }
    }
}
function stake(uint256 amount)
    public
    updateReward(_msgSender())
    checkStart
{
    require(amount > 0, "Cannot stake 0");
    super.stake(amount);
    emit Staked(_msgSender(), amount);
}

// stake visibility is public as overriding LPTokenWrapper's stake() function