contract NoMintRewardPool is LPTokenWrapper, IRewardDistributionRecipient {
    using Address for address;
    using SafeMath for uint256;

    IERC20 public rewardToken; // underlying asset of this pool / what is getting rewarded to users.
    uint256 public duration; // making it not a constant is less gas efficient, but portable.
    address public governance;
    address public agent;

    uint256 public periodFinish = 0; // timestamp when the period ends and this pool stops functioning.
    uint256 public rewardRate = 0;
    uint256 public lastUpdateTime;
    uint256 public rewardPerTokenStored;
    mapping(address => uint256) public userRewardPerTokenPaid;
    mapping(address => uint256) public rewards;

    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 RewardDenied(address indexed user, uint256 reward);

    /**
    *   1. Refreshes the contract data (reward per token & last applicable timestamp). 
    *   2. Updates the reward of the account.
    */
    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = lastTimeRewardApplicable();
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }


    /**
    * Set the reward, lpToken, duration, 
    * and rewardDistribution for this pool.
    */
    constructor(address _rewardToken,
        address _lpToken,
        uint256 _duration,
        address _rewardDistribution) public
    IRewardDistributionRecipient(_rewardDistribution)
    {
        rewardToken = IERC20(_rewardToken);
        lpToken = IERC20(_lpToken);
        duration = _duration;
        governance = msg.sender;
    }

    // Get current timestamp, or last valid timestamp (= period end)
    function lastTimeRewardApplicable() public view returns (uint256) {
        return Math.min(block.timestamp, periodFinish);
    }

    function setGovernance(address _gov) external {
        require(msg.sender == governance, '!governance');
        governance = _gov;
    }

    function setAgent(address _agent) external {
        require(msg.sender == governance, '!governance');
        agent = _agent;
    }

    /**
    * This fn computes the number of `rewardToken` someone gets
    * based on the amount of LP tokens they have staked in this contract.
    */
    function rewardPerToken() public view returns (uint256) {
        if (totalSupply() == 0) {
            return rewardPerTokenStored;
        }
        return
            rewardPerTokenStored.add(
                lastTimeRewardApplicable()
                    .sub(lastUpdateTime)
                    .mul(rewardRate)
                    .mul(1e18)
                    .div(totalSupply())
            );
    }

    /**
    * Compute amount earned by:
    * - getting the balance of the account
    * - multiplying it by the (current reward per token minus the one the user has)
    * - dividing by 18 (to get decimals right)
    * - adding the past paid out rewards
    */
    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.
    *
    * This is used to stake your LP tokens, so the contract takes them into account.
    */ 
    function stake(uint256 amount) public updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        super.stake(amount);
        emit Staked(msg.sender, amount);
    }

    /**
    * Reverse of the `stake` function.
    *
    * This function does not get the reward, just unstakes.
    * If you want to get the reward in one tx, call exit() below.
    */
    function withdraw(uint256 amount) public updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        super.withdraw(amount);
        emit Withdrawn(msg.sender, amount);
    }
    
    /**
    * Sends all relevant staked tokens and rewards to user.
    */ 
    function exit() external {
        withdraw(balanceOf(msg.sender));
        getReward();
    }

    /**
    * A push mechanism for accounts that have not claimed their rewards for a long time.
    * The implementation is semantically analogous to getReward(), but uses a push pattern
    * instead of pull pattern.
    * Can only be called by governance (onlyGovernance).
    */
    function pushReward(address recipient) public updateReward(recipient) {
        require(msg.sender == governance || msg.sender == agent, '!governance');
        uint256 reward = earned(recipient);
        if (reward > 0) {
            rewards[recipient] = 0;
            rewardToken.safeTransfer(recipient, reward);
            emit RewardPaid(recipient, reward);
        }
    }

    /**
    * Called by the user himself to get the reward.
    */ 
    function getReward() public updateReward(msg.sender) {
        uint256 reward = earned(msg.sender);
        if (reward > 0) {
            rewards[msg.sender] = 0;
            rewardToken.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }

    /**
    * This Fn is called by the FeeRewardForwarder (onlyRewardDistribution)
    * It is invoked after the Forwarder sends the funds (fee) to this contract.
    * Calling it results in a recalculation of the `rewardRate`.
    */
    function notifyRewardAmount(uint256 reward)
        external
        onlyRewardDistribution
        updateReward(address(0))
    {
        // overflow fix according to https://sips.synthetix.io/sips/sip-77
        require(reward < uint(-1) / 1e18, "the notified reward cannot invoke multiplication overflow");

        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);
    }
    
    function getFullSharePrice() public view returns (uint256) {
        if (totalSupply() == 0) {
            return rewardRate;
        } else {
            return rewardRate.div(totalSupply());
        }
    }
}
/*
 *   This pool doesn't mint anything.
 *   The rewards should be first transferred to this pool,
 *   then the pool get "notified" by calling `notifyRewardAmount`.
 */
function notifyRewardAmount(uint256 reward)
    external
    onlyRewardDistribution
    updateReward(address(0))
{
    // overflow fix according to https://sips.synthetix.io/sips/sip-77
    require(reward < uint(-1) / 1e18, "the notified reward cannot invoke multiplication overflow");

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

/**
 * This Fn is called by the FeeRewardForwarder (onlyRewardDistribution)
 * It is invoked after the Forwarder sends the funds (fee) to this contract.
 * Calling it results in a recalculation of the `rewardRate`.
 */