abstract contract RewardPoolBase is Ownable {
    mapping(address => Stake) internal staks; // token staks by account
    uint256 public staksScoresSum; // total staks scores
    uint256 public claimStaksScoresSum; // total staked scores, in use for claim reward pool
    uint256 public claimRewardPool; // reward pool to claim
    uint256 public claimRewardPoolStartCount; // reward pool to claim on start
    uint256 public nextIntervalTime; // time of new interval
    uint256 public intervalMinutes = 10080; // interval length
    uint256 public intervalNumber; // number of current interval (if pool is not started the value is 0)
    uint256 public totalRewardClaimed; // total, that claimed by users
    bool public enabled; // if true, than pool is enabled

    function start() external onlyOwner {
        require(intervalNumber == 0, "reward pool alredy started");
        beforeStart();
        nextIntervalTime = block.timestamp + intervalMinutes * 1 minutes;
        intervalNumber = 1;
        claimRewardPoolStartCount = claimRewardPool;
        enabled = true;
    }

    function setIntervalTimer(uint256 newIntervalMinutes) external onlyOwner {
        intervalMinutes = newIntervalMinutes;
    }

    function setIsEnabled(bool newEnabled) external onlyOwner {
        enabled = newEnabled;
    }

    function nextIntervalLapsedTime() external view returns (uint256) {
        if (block.timestamp >= nextIntervalTime) return 0;
        return nextIntervalTime - block.timestamp;
    }

    function getRewardCount(address account) public view returns (uint256) {
        return _getRewardCount(staks[account]);
    }

    function _getRewardCount(Stake storage stake)
        internal
        view
        returns (uint256)
    {
        if (
            stake.scores == 0 || stake.lastGrantIntervalNumber >= intervalNumber
        ) return 0;
        return (stake.scores * claimRewardPoolStartCount) / claimStaksScoresSum;
    }

    function _grantReward(
        address account,
        Stake storage stake,
        uint256 reward
    ) private {
        if (reward > claimRewardPool) reward = claimRewardPool;
        if (reward == 0) return;
        unchecked {
            claimRewardPool -= reward;
            totalRewardClaimed += reward;
        }
        // grant reward
        transferRewardTo(account, reward);
        // use stake
        stake.lastGrantIntervalNumber = intervalNumber;
    }

    function claimReward() external {
        tryNextInterval();
        Stake storage stake = staks[msg.sender];
        uint256 reward = _getRewardCount(stake);
        require(reward > 0, "has no reward");
        _grantReward(msg.sender, stake, reward);
    }

    function removeStake() external {
        tryNextInterval();
        Stake storage stake = staks[msg.sender];
        _grantReward(msg.sender, stake, _getRewardCount(stake)); // try grant reward if change stack
        require(stake.scores > 0, "stake scores is 0");
        removeStake(msg.sender, stake);
        staksScoresSum -= stake.scores;        
        delete staks[msg.sender];
    }

    function tryNextInterval() public {
        // try to go into next  interval
        if (block.timestamp < nextIntervalTime) return;
        // save total staks
        claimStaksScoresSum = staksScoresSum;
        // update reward pools
        claimRewardPool = getRewardsTotal();
        claimRewardPoolStartCount = claimRewardPool;
        // set the next interval
        ++intervalNumber;
        nextIntervalTime = block.timestamp + intervalMinutes * 1 minutes;
    }

    function _updateScores(Stake storage stake, uint256 newScores) internal {
        require(intervalNumber > 0 && enabled, "reward pool not started");
        tryNextInterval();
        _grantReward(msg.sender, stake, _getRewardCount(stake));
        if (stake.scores == newScores) return;
        if (stake.scores < newScores) {
            uint256 delta = newScores - stake.scores;
            staksScoresSum += delta;
            stake.scores += delta;
        } else {
            uint256 delta = stake.scores - newScores;
            staksScoresSum -= delta;
            stake.scores -= delta;
        }
        stake.lastGrantIntervalNumber = intervalNumber;
    }

    function getStake(address account) external view returns (Stake memory) {
        return _getStake(account);
    }

    function _getStake(address account)
        internal
        view
        virtual
        returns (Stake memory)
    {
        return staks[account];
    }

    function beforeStart() internal virtual;

    function transferRewardTo(address account, uint256 count) internal virtual;

    function removeStake(address account, Stake memory stake) internal virtual;

    // current total rewards count (for claims and accumulative)
    function getRewardsTotal() public view virtual returns (uint256);
}
//import "hardhat/console.sol";
function start() external onlyOwner {
    require(intervalNumber == 0, "reward pool alredy started");
    beforeStart();
    nextIntervalTime = block.timestamp + intervalMinutes * 1 minutes;
    intervalNumber = 1;
    claimRewardPoolStartCount = claimRewardPool;
    enabled = true;
}

// if true, than pool is enabled