contract StakingRewards is Ownable {

  using SafeMath for uint;

  IERC20 public lpt;
  IERC20 public rewardToken;

  uint public totalSupply;
  uint public DURATION = 7 days;
  IRewardReceiver public rewardReceiver;

  uint public starttime;
  uint public periodFinish = 0;
  uint public rewardRate = 0;
  uint public lastUpdateTime;
  uint public rewardPerTokenStored;

  mapping(address => uint) public userRewardPerTokenPaid;
  mapping(address => uint) public rewards;
  mapping(address => uint) public balanceOf;

  event RewardAdded(uint reward);
  event Staked(address indexed user, uint amount);
  event Withdrawn(address indexed user, uint amount);
  event RewardPaid(address indexed user, uint reward);

  constructor(address _rewardToken, address _lptoken, IRewardReceiver _rewardReceiver) public {
    rewardToken  = IERC20(_rewardToken);
    lpt = IERC20(_lptoken);
    rewardReceiver = _rewardReceiver;
    starttime = block.timestamp;
  }

  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;
    }
    _;
  }

  modifier pullRewards() {
    rewardReceiver.pushRewards();
    _;
  }

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

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

  function earned(address _account) public view returns (uint) {
    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(uint _amount) public pullRewards updateReward(msg.sender) checkStart {
    require(_amount > 0, "Cannot stake 0");
    totalSupply = totalSupply.add(_amount);
    balanceOf[msg.sender] = balanceOf[msg.sender].add(_amount);
    lpt.transferFrom(msg.sender, address(this), _amount);
    emit Staked(msg.sender, _amount);
  }

  function withdraw(uint _amount) public pullRewards updateReward(msg.sender) checkStart {
    require(_amount > 0, "Cannot withdraw 0");
    totalSupply = totalSupply.sub(_amount);
    balanceOf[msg.sender] = balanceOf[msg.sender].sub(_amount);
    lpt.transfer(msg.sender, _amount);
    emit Withdrawn(msg.sender, _amount);
  }

  function exit() public {
    withdraw(balanceOf[msg.sender]);
    getReward();
  }

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

  function notifyRewardAmount(uint _reward)
    public
    updateReward(address(0))
  {
    require(msg.sender == owner || msg.sender == address(rewardReceiver), "invalid reward source");

    if (block.timestamp > starttime) {
      if (block.timestamp >= periodFinish) {
        rewardRate = _reward.div(DURATION);
      } else {
        uint remaining = periodFinish.sub(block.timestamp);
        uint 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(uint _amount) public pullRewards updateReward(msg.sender) checkStart {
  require(_amount > 0, "Cannot stake 0");
  totalSupply = totalSupply.add(_amount);
  balanceOf[msg.sender] = balanceOf[msg.sender].add(_amount);
  lpt.transferFrom(msg.sender, address(this), _amount);
  emit Staked(msg.sender, _amount);
}

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