contract RznUsdtFarming is FarmingTokenWrapper, RewardsDistributionRecipient {

    using StableMath for uint256;

    IERC20 public rewardsToken;

    uint256 public constant ONE_DAY = 1 days;
    
    uint256 public farmingDuration = 0;

    // Timestamp for current period finish
    uint256 public periodFinish = 0;
    // RewardRate for the rest of the PERIOD
    uint256 public rewardRate = 0;
    // Last time any user took action
    uint256 public lastUpdateTime = 0;
    // Ever increasing rewardPerToken rate, based on % of total supply
    uint256 public rewardPerTokenStored = 0;
    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

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

    constructor(
        address _farmingToken,
        address _rewardsToken,
        address _rewardsDistributor,
        uint256 _farmingDurationDays
    )
        public
        FarmingTokenWrapper(_farmingToken)
        RewardsDistributionRecipient(_rewardsDistributor)
    {
        rewardsToken = IERC20(_rewardsToken);
        farmingDuration = _farmingDurationDays.mul(ONE_DAY);
    }

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

    /***************************************
                    ACTIONS
    ****************************************/

    function farm(uint256 _amount)
        external
    {
        _farm(msg.sender, _amount);
    }

    function _farm(address _beneficiary, uint256 _amount)
        internal
        updateReward(_beneficiary)
    {
        require(_amount > 0, "Cannot stake 0");
        super._farm(_beneficiary, _amount);
        emit Staked(_beneficiary, _amount, msg.sender);
    }

    function unfarm() external {
        withdraw(balanceOf(msg.sender));
        claimReward();
    }

    function withdraw(uint256 _amount)
        public
        updateReward(msg.sender)
    {
        require(_amount > 0, "Cannot withdraw 0");
        _withdraw(_amount);
        emit Withdrawn(msg.sender, _amount);
    }

    function claimReward()
        public
        updateReward(msg.sender)
    {
        uint256 reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            rewardsToken.transfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }


    /***************************************
                    GETTERS
    ****************************************/

    function getRewardToken()
        external
        view
        returns (IERC20)
    {
        return rewardsToken;
    }

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

    function rewardPerToken()
        public
        view
        returns (uint256)
    {
        uint256 stakedTokens = totalSupply();
        if (stakedTokens == 0) {
            return rewardPerTokenStored;
        }
        uint256 rewardUnitsToDistribute = rewardRate.mul(lastTimeRewardApplicable().sub(lastUpdateTime));
        uint256 unitsToDistributePerToken = rewardUnitsToDistribute.divPrecisely(stakedTokens);
        return rewardPerTokenStored.add(unitsToDistributePerToken);
    }

    function earned(address _account)
        public
        view
        returns (uint256)
    {
        uint256 userRewardDelta = rewardPerToken().sub(userRewardPerTokenPaid[_account]);
        uint256 userNewReward = balanceOf(_account).mulTruncate(userRewardDelta);
        return rewards[_account].add(userNewReward);
    }


    /***************************************
                    ADMIN
    ****************************************/

    function notifyRewardAmount(uint256 _reward)
        external
        onlyRewardsDistributor
        updateReward(address(0))
    {
        uint256 currentTime = block.timestamp;
        // If previous period over, reset rewardRate
        if (currentTime >= periodFinish) {
            rewardRate = _reward.div(farmingDuration);
        }
        // If additional reward to existing period, calc sum
        else {
            uint256 remaining = periodFinish.sub(currentTime);
            uint256 leftover = remaining.mul(rewardRate);
            rewardRate = _reward.add(leftover).div(farmingDuration);
        }

        lastUpdateTime = currentTime;
        periodFinish = currentTime.add(farmingDuration);

        emit RewardAdded(_reward);
    }
    
    function sendRewardTokens(uint256 _amount) public onlyRewardsDistributor {
        require(rewardsToken.transferFrom(msg.sender, address(this), _amount), "Transfering not approved!");
    }
    
    function withdrawRewardTokens(address receiver, uint256 _amount) public onlyRewardsDistributor {
        require(rewardsToken.transfer(receiver, _amount), "Not enough tokens on contract!");
    }
}
function getRewardToken()
    external
    view
    returns (IERC20)
{
    return rewardsToken;
}

/***************************************
 GETTERS
 ****************************************/