contract StakePoolWEV is ReentrancyGuard, LPTokenWrapper, RewardDistributionRecipient {
    IERC20 public wev = IERC20(0xFB9fc4CCC2538172fe76F7dC231a6969950E57c8); //WEV

    uint256 public constant DURATION = 7 days;

    uint256 public initreward = 630000*1e18;
    uint256 public starttime = 1601568000; //utc+0 October 1, 2020 @ 4:00:00 pm
    uint256 public periodFinish = 0;
    uint256 public rewardRate = 0;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;
    uint256 public punishPeriod = 3 days; //can be changed by timelock
    uint256 public punishPercent = 10; // can be changed by timelock, default 10% reward wev. The principal is safe.
    uint256 public flashLoanFee = 35; //flash loan fee in basis points default 0.35%

    address public timelock = address(0x2a31682cc45263505f4EeF7110f3c9e058616213);  // timelock contract
    address public treasuryTimelock = address(0x00cc0e1C488E188922aAc9F1703A6F1961c5854b);  // treasury timelock contract
    address public treasury = address(0x2d78100A0dCf8799Dc57f0D541e7A91E30B25e61);  // treasury, can be changed by treasury timelock


    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;
    mapping(address => uint256) public lastStakeTime;
    mapping(address => bool) public flashLoanWhitelist;


    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 PunishPaid(address indexed user, uint256 punish);
    event NewPunishPeriod(uint256 oldPunishPeriod, uint256 newPunishPeriod);
    event NewPunishPercent(uint256 oldPunishPercent, uint256 newPunishPercent);
    event NewTreasury(address oldTreasury, address newTreasury);
    event FlashLoan(
        address indexed _target,
        address indexed _reserve,
        uint256 _amount,
        uint256 _totalFee,
        uint256 _timestamp
    );
    event NewFlashLoanFee(uint256 oldFee, uint256 newFee);
    event SetFlashLoanWhitelist(address flasher, bool status);


    modifier onlyTreasuryTimelock() {
        require(msg.sender == treasuryTimelock, "caller is not the treasuryTimelock");
        _;
    }
    modifier onlyTimelock() {
        require(msg.sender == timelock, "caller is not the timelock");
        _;
    }
    modifier onlyWhitelist() {
        require(flashLoanWhitelist[msg.sender], "caller is not in whitelist");
        _;
    }
    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }
    modifier checkhalve(){
        if (block.timestamp >= periodFinish) {
            initreward = initreward.mul(50).div(100); 
            IWEV(address(wev)).mint(address(this),initreward);

            rewardRate = initreward.div(DURATION);
            periodFinish = block.timestamp.add(DURATION);
            emit RewardAdded(initreward);
        }
        _;
    }
    modifier checkStart(){
        require(block.timestamp > starttime,"not start");
        _;
    }

    constructor () public LPTokenWrapper(wev) {}

    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]);
    }

    // stake visibility is public as overriding LPTokenWrapper's stake() function
    function stake(uint256 amount) public updateReward(msg.sender) checkhalve checkStart{ 
        require(amount > 0, "Cannot stake 0");
        super.stake(amount);
        lastStakeTime[msg.sender] = block.timestamp;
        emit Staked(msg.sender, amount);
    }
    function punishmentCalc(address holder) public view returns (uint256 punishPaid, uint256 rewardWithPunishPaid) {
        uint256 reward = earned(holder);
        if (block.timestamp < lastStakeTime[holder] + punishPeriod) { //should be punished
            punishPaid = reward.mul(punishPercent).div(100); // default 10%
            rewardWithPunishPaid = reward.mul(100 - punishPercent).div(100); // default 90%
        } else {
            rewardWithPunishPaid = reward; 
        }
    }
    function withdraw(uint256 amount) public updateReward(msg.sender) checkhalve checkStart{
        require(amount > 0, "Cannot withdraw 0");

        (uint256 punishPaid, uint256 reward) =  punishmentCalc(msg.sender); 

        if (punishPaid > 0) {
            wev.safeTransfer(treasury, punishPaid); // charge fine
            emit PunishPaid(msg.sender, punishPaid);
        }
        rewards[msg.sender] = reward;  

        super.withdraw(amount);
        emit Withdrawn(msg.sender, amount);
    }
    function _withdrawAll() internal updateReward(msg.sender) checkhalve checkStart returns (uint256) {
        uint256 amount = balanceOf(msg.sender);
        super.withdraw(amount);
        emit Withdrawn(msg.sender, amount);
        
        return amount;
    }
    
    function exit() public returns (uint256) {
        uint256 amount = _withdrawAll();
        return amount.add(getReward());
    }

    function claimAndStake() external {
        uint256 amount = exit();
        stake(amount);
    }
    
    function getReward() public updateReward(msg.sender) checkhalve checkStart returns (uint256) {
        uint256 amount = 0;
        
        if (earned(msg.sender) > 0) {

            (uint256 punishPaid, uint256 reward) =  punishmentCalc(msg.sender);

            rewards[msg.sender] = 0;
            if (punishPaid > 0) {
                wev.safeTransfer(treasury, punishPaid); // charge fine
                emit PunishPaid(msg.sender, punishPaid);
            }
            wev.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
            amount = reward;
        }
        
        return amount;
    }
    function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _params) public nonReentrant onlyWhitelist {

        uint256 availableLiquidityBefore = wev.balanceOf(address(this));
        require(
            availableLiquidityBefore >= _amount,
            "There is not enough liquidity available to borrow"
        );
        uint256 amountFee = _amount.mul(flashLoanFee).div(10000); // fee = amount * 0.35%
       require(
            amountFee > 0,
            "The requested amount is too small for a flashLoan."
        );
        //get the FlashLoanReceiver instance
        IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver);
        //transfer funds to the receiver
        wev.transfer(_receiver, _amount);
        //execute action of the receiver
        receiver.executeOperation(_reserve, _amount, amountFee, _params);
        uint256 availableLiquidityAfter = wev.balanceOf(address(this));

        require(
            availableLiquidityAfter == availableLiquidityBefore.add(amountFee),
            "The actual balance of the protocol is inconsistent"
        );
        _updateReward(amountFee);
        emit FlashLoan(_receiver, _reserve, _amount, amountFee, block.timestamp);
    }
    function _updateReward(
        uint256 reward
    ) internal {
        uint256 remaining = periodFinish.sub(block.timestamp);
        uint256 leftover = remaining.mul(rewardRate);
        rewardRate = reward.add(leftover).div(DURATION);
    }
    function notifyRewardAmount(uint256 reward)
        external
        onlyRewardDistribution
        updateReward(address(0))
    {
        if (block.timestamp >= periodFinish) {
            rewardRate = reward.div(DURATION);
        } else {
            _updateReward(reward);
        }
        IWEV(address(wev)).mint(address(this),reward);
        lastUpdateTime = block.timestamp;
        periodFinish = block.timestamp.add(DURATION);
        emit RewardAdded(reward);
    }
    function setPunishPeriod(uint256 newPunishPeriod) external onlyTimelock {
        require(newPunishPeriod <= 30 days, "punishPeriod too long");
        uint256 oldPunishPeriod = punishPeriod;
        punishPeriod = newPunishPeriod;

        emit NewPunishPeriod(oldPunishPeriod, punishPeriod);
    }
    function setPunishPercent(uint256 newPunishPercent) external onlyTimelock {
        require(newPunishPercent <= 100, "punishPercent too heigh");
        uint256 oldPunishPercent = punishPercent;
        punishPercent = newPunishPercent;

        emit NewPunishPercent(oldPunishPercent, punishPercent);
    }
    function setTreasury(address newTreasury) external onlyTreasuryTimelock {

        address oldTreasury = treasury;
        treasury = newTreasury;

        emit NewTreasury(oldTreasury, treasury);
    }
    function setFlashLoanFee(uint256 newFee) external onlyTimelock {

        uint256 oldFee = flashLoanFee;
        flashLoanFee = newFee;

        emit NewFlashLoanFee(oldFee, flashLoanFee);
    }
    function setFlashLoanWhitelist(address borrower, bool status) external onlyTimelock {
        require(status != flashLoanWhitelist[borrower], "borrower status error");

        flashLoanWhitelist[borrower] = status;
        emit SetFlashLoanWhitelist(borrower, status); 
    }
}
function stake(uint256 amount) public updateReward(msg.sender) checkhalve checkStart{ 
    require(amount > 0, "Cannot stake 0");
    super.stake(amount);
    lastStakeTime[msg.sender] = block.timestamp;
    emit Staked(msg.sender, amount);
}

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