contract WolfStakingRewards is IStakingRewards, RewardsDistributionRecipient, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    /* ========== STATE VARIABLES ========== */

    IERC20 public rewardsToken;
    IERC20 public stakingToken;
    uint256[] public rewardsDuration = [1611861360,1614413820,1616957280,1619407860,1622027640,1624560000,1627007820,1629633720,1632182100,1634741820,1637312280,1639802160];
    address public owner;
    
    uint public genesisEventEnd = 1639802160;

    mapping(address => mapping(uint256 => uint256)) public userRewardPerTokenPaid;
    mapping(address => mapping(uint256 => uint256)) public rewards;
    mapping(uint256 => uint256) public rewardRate;
    mapping(uint256 => uint256) public periodFinish;
    mapping(uint256 => uint256) public lastUpdateTime;
    mapping(uint256 => uint256) public rewardPerTokenStored;
    mapping(uint256 => uint256) public _totalSupplyPerPool;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(uint256 => uint256)) private _balancesPerPool;

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _rewardsDistribution,
        address _rewardsToken,
        address _stakingToken
    ) public {
        rewardsToken = IERC20(_rewardsToken);
        stakingToken = IERC20(_stakingToken);
        rewardsDistribution = _rewardsDistribution;
        owner = msg.sender;
    }

    /* ========== VIEWS ========== */
    
    function totalSupplyPerPool(uint256 pool) external view returns (uint256) {
        return _totalSupplyPerPool[pool];
    }

    function balanceOfPerPool(address account, uint256 pool) external view returns (uint256) {
        return _balancesPerPool[account][pool];
    }

    function lastTimeRewardApplicable(uint256 pool) public view returns (uint256) {
        return Math.min(block.timestamp, periodFinish[pool]);
    }

    function rewardPerToken(uint256 pool) public view returns (uint256) {
        if (_totalSupplyPerPool[pool] == 0) {
            return rewardPerTokenStored[pool];
        }
        return
            rewardPerTokenStored[pool].add(
                lastTimeRewardApplicable(pool).sub(lastUpdateTime[pool]).mul(rewardRate[pool]).mul(1e18).div(_totalSupplyPerPool[pool])
            );
    }

    function earned(address account, uint256 pool) public view returns (uint256) {
            return _balancesPerPool[account][pool].mul(rewardPerToken(pool).sub(userRewardPerTokenPaid[account][pool])).div(1e18).add(rewards[account][pool]);
    }

    function getRewardForDuration(uint256 pool) external view returns (uint256) {
        return rewardRate[pool].mul(rewardsDuration[pool]);
    }

    /* ========== MUTATIVE FUNCTIONS ========== */

    function stake(uint256 amount, uint pool) external nonReentrant updateReward(msg.sender, pool) {
        require(amount > 0, "Cannot stake 0");
        _totalSupplyPerPool[pool] = _totalSupplyPerPool[pool].add(amount);
        _balancesPerPool[msg.sender][pool] = _balancesPerPool[msg.sender][pool].add(amount);
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount, pool);
    }

    function withdraw(uint256 amount, uint pool) public nonReentrant updateReward(msg.sender, pool) {
        require(amount > 0, "Cannot withdraw 0");
        require(now > rewardsDuration[pool], "Pool duration is not over yet!");
        _totalSupplyPerPool[pool] = _totalSupplyPerPool[pool].sub(amount);
        _balancesPerPool[msg.sender][pool] = _balancesPerPool[msg.sender][pool].sub(amount);
        stakingToken.safeTransfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount, pool);
    }

    function getReward(uint pool) public nonReentrant updateReward(msg.sender, pool) {
        uint256 reward = rewards[msg.sender][pool];
        if (reward > 0) {
            rewards[msg.sender][pool] = 0;
            rewardsToken.safeTransfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward, pool);
        }
    }

    function exit(uint256 pool) external {
        require(now > rewardsDuration[pool], "Pool duration is not over yet!");
        withdraw(_balancesPerPool[msg.sender][pool], pool);
        getReward(pool);
    }

    /* ========== RESTRICTED FUNCTIONS ========== */
    
    function updateNotifyRewardAmount(uint256[] calldata reward) external onlyRewardsDistribution onlyOwner {
        require(reward.length > 0, "Reward amount should be for all pool");
        for (uint256 pool = 0; pool < reward.length; pool++) {
            notifyRewardAmount(reward[pool], pool);
        }
    }

    function notifyRewardAmount(uint256 reward, uint256 pool) internal onlyRewardsDistribution updateReward(address(0), pool) {
        if (block.timestamp >= periodFinish[pool]) {
            rewardRate[pool] = reward.div(rewardsDuration[pool]);
        } else {
            uint256 remaining = periodFinish[pool].sub(block.timestamp);
            uint256 leftover = remaining.mul(rewardRate[pool]);
            rewardRate[pool] = reward.add(leftover).div(rewardsDuration[pool]);
        }
        uint balance = rewardsToken.balanceOf(address(this));
        require(rewardRate[pool] <= balance.div(rewardsDuration[pool]), "Provided reward too high");
        lastUpdateTime[pool] = block.timestamp;
        periodFinish[pool] = block.timestamp.add(rewardsDuration[pool]);
        emit RewardAdded(reward, pool);
    }
    
    function withdrawRemainingBalance(uint256 amount, uint pool) external onlyOwner updateReward(address(0), pool){
        require(amount > 0, "Cannot withdraw 0");
        require(now > genesisEventEnd, "Genesis Event is Not Over!");
        rewardsToken.safeTransfer(owner, amount);
    }

    /* ========== MODIFIERS ========== */

    modifier updateReward(address account, uint256 pool) {
        rewardPerTokenStored[pool] = rewardPerToken(pool);
        lastUpdateTime[pool] = lastTimeRewardApplicable(pool);
        if (account != address(0)) {
            rewards[account][pool] = earned(account, pool);
            userRewardPerTokenPaid[account][pool] = rewardPerTokenStored[pool];
        }
        _;
    }
    
    modifier onlyOwner(){
        require(msg.sender == owner, "Caller is not an admin contract");
        _;
    }

    /* ========== EVENTS ========== */

    event RewardAdded(uint256 reward, uint256 pool);
    event Staked(address indexed user, uint256 amount, uint256 pool);
    event Withdrawn(address indexed user, uint256 amount, uint256 pool);
    event RewardPaid(address indexed user, uint256 reward, uint256 pool);
}
function totalSupplyPerPool(uint256 pool) external view returns (uint256) {
    return _totalSupplyPerPool[pool];
}

/* ========== VIEWS ========== */