abstract contract RewardableErc20 is ERC20, MaxWalletDynamic {
    uint256 public constant rewardIntervalDays = 7; // reward interval time
    uint256 public rewardInterval; // current reward interwal
    uint256 public rewardIntervalStartRewards; // current reward interwal start rewards
    uint256 public rewardIntervalLapsedRewards; // current reward interwal lapsed rewards
    uint256 public rewardedTotal; // total rewarded
    uint256 rewardIntervalStartTime; // time, when current reward interval starts
    mapping(address => uint256) _lastClaimedReward; // reward interwals by account claims
    uint256 startRewardsAccountsBalance; // all accounts balance on change reward interval

    function tryNextRewardInterval() public {
        if (rewardLapsedTime() > 0) return;
        _nextRewardInterval();
    }

    function _startRewardIntervals() internal {
        _nextRewardInterval();
    }

    function _nextRewardInterval() internal {
        rewardIntervalStartRewards = _balances[address(this)];
        rewardIntervalLapsedRewards = rewardIntervalStartRewards;
        rewardIntervalStartTime = block.timestamp;
        startRewardsAccountsBalance = getAccountsBalance();
        ++rewardInterval;
    }

    function getRewardForBalanceThisRewardInterval(uint256 balance)
        external
        view
        returns (uint256)
    {
        return
            _getRewardForBalance(
                balance,
                rewardIntervalStartRewards,
                startRewardsAccountsBalance
            );
    }

    function getRewardForBalanceTotal(uint256 balance)
        external
        view
        returns (uint256)
    {
        return
            _getRewardForBalance(
                balance,
                _balances[address(this)],
                getAccountsBalance()
            );
    }

    function _getRewardForBalance(
        uint256 balance,
        uint256 startRewardsCount,
        uint256 startBalanceTotal
    ) public view returns (uint256) {
        // if has no rewards on pool
        if (startBalanceTotal == 0) return 0;
        // get reward
        uint256 reward = (startRewardsCount * balance) /
            startBalanceTotal;
        // max wallet limitation
        uint256 maxWallet = getMaxWallet();
        if (balance + reward > maxWallet) reward = maxWallet - balance;
        // return reward
        return reward;
    }

    /// @dev current available reward for account
    function getRewardCount(address account) external view returns (uint256) {
        if (rewardLapsedTime() == 0)
            return _getRewardForBalance(
                _balances[account],
                _balances[address(this)],
                getAccountsBalance()
            );
        if (_lastClaimedReward[account] == rewardInterval) return 0;
        return
            _getRewardForBalance(
                _balances[account],
                rewardIntervalStartRewards,
                startRewardsAccountsBalance
            );
    }

    function canClaimReward(address account) external view returns (bool) {
        return rewardLapsedTime() == 0 || _canClaimReward(account);
    }

    function _canClaimReward(address account) internal view returns (bool) {
        return
            _lastClaimedReward[account] < rewardInterval && rewardInterval != 1;
    }

    function _tryClaimReward(address account) internal {
        tryNextRewardInterval();
        if (!_canClaimReward(account)) return;
        _claimReward(account);
    }

    function claimReward(address account) external {
        tryNextRewardInterval();
        require(_canClaimReward(account), "can not claim reward now");
        _claimReward(account);
    }

    function _claimReward(address account) private {
        _lastClaimedReward[account] = rewardInterval;
        uint256 reward = _getRewardForBalance(
            _balances[account],
            rewardIntervalStartRewards,
            startRewardsAccountsBalance
        );
        uint256 balance = _balances[account];

        if (reward > rewardIntervalLapsedRewards) return;
        _balances[address(this)] -= reward;
        rewardIntervalLapsedRewards -= reward;
        _balances[account] = balance + reward;
        rewardedTotal += reward;
        emit Transfer(address(this), account, reward);
    }

    function rewardLapsedTime() public view returns (uint256) {
        uint256 nextIntervalTime = rewardIntervalStartTime +
            rewardIntervalDays *
            1 days;
        if (block.timestamp >= nextIntervalTime) return 0;
        return nextIntervalTime - block.timestamp;
    }

    function getAccountsBalance() public view virtual returns (uint256);
}
function getRewardCount(address account) external view returns (uint256) {
    if (rewardLapsedTime() == 0)
        return _getRewardForBalance(
            _balances[account],
            _balances[address(this)],
            getAccountsBalance()
        );
    if (_lastClaimedReward[account] == rewardInterval) return 0;
    return
        _getRewardForBalance(
            _balances[account],
            rewardIntervalStartRewards,
            startRewardsAccountsBalance
        );
}

/// @dev current available reward for account