contract TREERewards is LPTokenWrapper, IRewardDistributionRecipient {
  IERC20 public rewardToken;
  uint256 public constant DURATION = 7 days;
  uint256 public constant PRECISION = 10**18;

  uint256 public starttime;
  uint256 public periodFinish = 0;
  uint256 public rewardRate = 0;
  uint256 public lastUpdateTime;
  uint256 public rewardPerTokenStored;
  mapping(address => uint256) public userRewardPerTokenPaid;
  mapping(address => uint256) public rewards;
  bool public initialized;

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

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

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

  function init(
    address _sender,
    uint256 _starttime,
    address _stakeToken,
    address _rewardToken
  ) public {
    require(!initialized, "Initialized");
    initialized = true;
    starttime = _starttime;
    rewardToken = IERC20(_rewardToken);
    _initStakeToken(_stakeToken);
    _transferOwnership(_sender);
  }

  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(PRECISION)
          .div(totalSupply())
      );
  }

  function earned(address account) public view returns (uint256) {
    return
      balanceOf(account)
        .mul(rewardPerToken().sub(userRewardPerTokenPaid[account]))
        .div(PRECISION)
        .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);
    }
  }

  function notifyRewardAmount(uint256 reward)
    external
    override
    onlyRewardDistribution
    updateReward(address(0))
  {
    if (block.timestamp > starttime) {
      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);
    } else {
      rewardRate = reward.div(DURATION);
      lastUpdateTime = starttime;
      periodFinish = starttime.add(DURATION);
      emit RewardAdded(reward);
    }
  }
}
function stake(uint256 amount)
  public
  override
  updateReward(msg.sender)
  checkStart
{
  require(amount > 0, "Cannot stake 0");
  super.stake(amount);
  emit Staked(msg.sender, amount);
}

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