contract Staking is Ownable {

    struct User {
        uint256 depositAmount;
        uint256 paidReflection;
    }

    using SafeMath for uint256;

    mapping (address => User) public users;

    uint256 public reflectionTillNowPerToken = 0;
    uint256 public lastUpdatedBlock;
    uint256 public rewardPerBlock;
    uint256 public totalOhmsReward = 0;
  

    StakedToken public stakedToken;
    OHMS public ohms;

    event Deposit(address user, uint256 amount);
    event Withdraw(address user, uint256 amount);
    event EmergencyWithdraw(address user, uint256 amount);
    event RewardClaimed(address user, uint256 amount);
    event RewardPerBlockChanged(uint256 oldValue, uint256 newValue);

    constructor (address _stakedToken, address _OHMS, uint256 _rewardPerBlock) public {
        stakedToken = StakedToken(_stakedToken);
        ohms = OHMS(_OHMS);
        rewardPerBlock = _rewardPerBlock;
        lastUpdatedBlock = block.number;
    }

    // Returns the amount of OHMS rewarded to stakers per block
    function setRewardPerBlock(uint256 _rewardPerBlock) public onlyOwner {
        update();
        emit RewardPerBlockChanged(rewardPerBlock, _rewardPerBlock);
        rewardPerBlock = _rewardPerBlock;
    }

    // Returns the amount of OHMS rewarded to stakers per block
    function getRewardPerBlock() external view returns (uint256){
        return rewardPerBlock;
    }

    // View function to see users staked balance on frontend.
    function getUserDepositAmount(address _user) external view returns (uint256){
        User storage user = users [_user];
        return user.depositAmount;
    }

    //  View function to see the amount staked in the contract in OHMS. 
    function getTotalStaked() external view returns (uint256){
        return stakedToken.balanceOf(address(this));
    }

    // View function to get the remaining reward balance in the contract in OHMS.
    function getTotalRemainingReward() external view returns (uint256){
        return ohms.balanceOf(address(this));
    }

    // Update reward variables of the pool to be up-to-date.
    function update() public {
        if (block.number <= lastUpdatedBlock) {
            return;
        }

        uint256 totalStakedToken;
        if(stakedToken.balanceOf(address(this)) > 0){
            totalStakedToken = stakedToken.balanceOf(address(this));
        }
        else{
            totalStakedToken = 1;
        }

        uint256 rewardAmount = (block.number - lastUpdatedBlock).mul(rewardPerBlock);
        totalOhmsReward = totalOhmsReward.add(rewardAmount);
        uint256 reflectionRewardAmount = ohms.reflectionFromToken(rewardAmount, false).div(1000);
        
        reflectionTillNowPerToken = reflectionTillNowPerToken.add(reflectionRewardAmount.div(totalStakedToken));
        lastUpdatedBlock = block.number;
    }

    // View function to see pending reward on frontend.
    function pendingReward(address _user) external view returns (uint256) {
        User storage user = users[_user];
        uint256 accReflectionPerToken = reflectionTillNowPerToken;
        uint256 totalStakedToken;
        if(stakedToken.balanceOf(address(this)) > 0){
            totalStakedToken = stakedToken.balanceOf(address(this));
        }
        else{
            totalStakedToken = 1;
        }

        if (block.number > lastUpdatedBlock) {
            uint256 rewardAmount = (block.number - lastUpdatedBlock).mul(rewardPerBlock);
            uint256 reflectionReward = ohms.reflectionFromToken(rewardAmount,false).div(1000);
            accReflectionPerToken = accReflectionPerToken.add(reflectionReward.div(totalStakedToken));
        }
        return ohms.tokenFromReflection((user.depositAmount.mul(accReflectionPerToken).sub(user.paidReflection)).mul(1000));
    }

    function deposit(uint256 amount) public {
        User storage user = users[msg.sender];
        update();

        if (user.depositAmount > 0) {
            uint256 _pendingReflection = (user.depositAmount.mul(reflectionTillNowPerToken).sub(user.paidReflection)).mul(1000);
            uint256 _ohmsReward = ohms.tokenFromReflection(_pendingReflection);
            if(_ohmsReward > 0){                
                ohms.transfer(address(msg.sender), _ohmsReward);
                emit RewardClaimed(msg.sender, _ohmsReward);
            }
        }


        stakedToken.transferFrom(address(msg.sender), address(this), amount);
        emit Deposit(msg.sender, amount);

        user.depositAmount = user.depositAmount.add(amount);
        user.paidReflection = user.depositAmount.mul(reflectionTillNowPerToken);
    }

    function withdraw(uint256 amount) public {
        User storage user = users[msg.sender];
        require(user.depositAmount >= amount, "withdraw amount exceeds deposited amount");
        update();

        uint256 _pendingReflection = (user.depositAmount.mul(reflectionTillNowPerToken).sub(user.paidReflection)).mul(1000);
        uint256 _ohmsReward = ohms.tokenFromReflection(_pendingReflection);
        if(_ohmsReward > 0){                
            ohms.transfer(address(msg.sender), _ohmsReward);
            emit RewardClaimed(msg.sender, _ohmsReward);
        }
        
        stakedToken.transfer(address(msg.sender), amount);
        emit Withdraw(msg.sender, amount);

        user.depositAmount = user.depositAmount.sub(amount);
        user.paidReflection = user.depositAmount.mul(reflectionTillNowPerToken);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw() public {
        User storage user = users[msg.sender];

        stakedToken.transfer(address(msg.sender), user.depositAmount);

        emit EmergencyWithdraw(msg.sender, user.depositAmount);

        user.depositAmount = 0;
        user.paidReflection = 0;
    }
}
function setRewardPerBlock(uint256 _rewardPerBlock) public onlyOwner {
    update();
    emit RewardPerBlockChanged(rewardPerBlock, _rewardPerBlock);
    rewardPerBlock = _rewardPerBlock;
}

// Returns the amount of OHMS rewarded to stakers per block