contract GlobalsAndUtility is ERC20 {
    /*  XfLobbyEnter
     */
    event XfLobbyEnter(
        uint256 timestamp,
        uint256 enterDay,
        uint256 indexed entryIndex,
        uint256 indexed rawAmount
    );
    /*  XfLobbyExit
     */
    event XfLobbyExit(
        uint256 timestamp,
        uint256 enterDay,
        uint256 indexed entryIndex,
        uint256 indexed xfAmount,
        address indexed referrerAddr
    );
    /*  DailyDataUpdate
     */
    event DailyDataUpdate(
        address indexed updaterAddr,
        uint256 timestamp,
        uint256 beginDay,
        uint256 endDay
    );
    /*  StakeStart
     */
    event StakeStart(
        uint40 indexed stakeId,
        address indexed stakerAddr,
        uint256 stakedSuns,
        uint256 stakeShares,
        uint256 stakedDays
    );
    /*  StakeGoodAccounting
     */
    event StakeGoodAccounting(
        uint40 indexed stakeId,
        address indexed stakerAddr,
        address indexed senderAddr,
        uint256 stakedSuns,
        uint256 stakeShares,
        uint256 payout,
        uint256 penalty
    );
    /*  StakeEnd
     */
    event StakeEnd(
        uint40 indexed stakeId,
        uint40 prevUnlocked,
        address indexed stakerAddr,
        uint256 lockedDay,
        uint256 servedDays,
        uint256 stakedSuns,
        uint256 stakeShares,
        uint256 payout,
        uint256 penalty,
        uint256 stakeReturn
    );
    /*  ShareRateChange
     */
    event ShareRateChange(
        uint40 indexed stakeId,
        uint256 timestamp,
        uint256 newShareRate
    );
    //uint256 internal constant ROUND_TIME = 1 days;
    //uint256 internal constant ROUND_TIME = 2 hours;
    uint256 public ROUND_TIME;
    //uint256 internal constant ROUND_TIME = 5 minutes;
    //uint256 internal constant LOTERY_ENTRY_TIME = 1 hours;
    //uint256 internal constant LOTERY_ENTRY_TIME = 20 minutes;
    uint256 public LOTERY_ENTRY_TIME;
    address public defaultReferrerAddr;
    /* Flush address */
    address payable public flushAddr;
    uint256 internal firstAuction = uint256(-1);
    uint256 internal LAST_FLUSHED_DAY = 0;
    /* ERC20 constants */
    string public constant name = "Jackpot Ethereum";
    string public constant symbol = "JETH";
    uint8 public constant decimals = 8;
    uint256 public LAUNCH_TIME; // = 1606046700;
    uint256 public dayNumberBegin; // = 2;
    /* Start of claim phase */
    uint256 internal constant CLAIM_STARTING_AMOUNT =
        2500000 * (10**uint256(decimals));
    uint256 internal constant CLAIM_LOWEST_AMOUNT =
        1000000 * (10**uint256(decimals));
    /* Number of words to hold 1 bit for each transform lobby day */
    uint256 internal constant XF_LOBBY_DAY_WORDS = ((1 + (50 * 7)) + 255) >> 8;
    /* Stake timing parameters */
    uint256 internal constant MIN_STAKE_DAYS = 1;
    uint256 internal constant MAX_STAKE_DAYS = 180; // Approx 0.5 years
    uint256 internal constant EARLY_PENALTY_MIN_DAYS = 90;
    //uint256 private constant LATE_PENALTY_GRACE_WEEKS = 2;
    uint256 internal constant LATE_PENALTY_GRACE_DAYS = 2 * 7;
    //uint256 private constant LATE_PENALTY_SCALE_WEEKS = 100;
    uint256 internal constant LATE_PENALTY_SCALE_DAYS = 100 * 7;
    /* Stake shares Longer Pays Better bonus constants used by _stakeStartBonusSuns() */
    //uint256 private constant LPB_BONUS_PERCENT = 20;
    //uint256 private constant LPB_BONUS_MAX_PERCENT = 200;
    uint256 internal constant LPB = (18 * 100) / 20; /* LPB_BONUS_PERCENT */
    uint256 internal constant LPB_MAX_DAYS = (LPB * 200) / 100; /* LPB_BONUS_MAX_PERCENT */
    /* Stake shares Bigger Pays Better bonus constants used by _stakeStartBonusSuns() */
    //uint256 private constant BPB_BONUS_PERCENT = 10;
    //uint256 private constant BPB_MAX_JACKPOT = 7 * 1e6;
    uint256 internal constant BPB_MAX_SUNS =
        7 *
            1e6 * /* BPB_MAX_JACKPOT */
            (10**uint256(decimals));
    uint256 internal constant BPB = (BPB_MAX_SUNS * 100) / 10; /* BPB_BONUS_PERCENT */
    /* Share rate is scaled to increase precision */
    uint256 internal constant SHARE_RATE_SCALE = 1e5;
    /* Share rate max (after scaling) */
    uint256 internal constant SHARE_RATE_UINT_SIZE = 40;
    uint256 internal constant SHARE_RATE_MAX = (1 << SHARE_RATE_UINT_SIZE) - 1;
    /* weekly staking bonus */
    uint8 internal constant BONUS_DAY_SCALE = 2;
    /* Globals expanded for memory (except _latestStakeId) and compact for storage */
    struct GlobalsCache {
        uint256 _lockedSunsTotal;
        uint256 _nextStakeSharesTotal;
        uint256 _shareRate;
        uint256 _stakePenaltyTotal;
        uint256 _dailyDataCount;
        uint256 _stakeSharesTotal;
        uint40 _latestStakeId;
        uint256 _currentDay;
    }
    struct GlobalsStore {
        uint256 lockedSunsTotal;
        uint256 nextStakeSharesTotal;
        uint40 shareRate;
        uint256 stakePenaltyTotal;
        uint16 dailyDataCount;
        uint256 stakeSharesTotal;
        uint40 latestStakeId;
    }
    GlobalsStore public globals;
    /* Daily data */
    struct DailyDataStore {
        uint256 dayPayoutTotal;
        uint256 dayDividends;
        uint256 dayStakeSharesTotal;
    }
    mapping(uint256 => DailyDataStore) public dailyData;
    /* Stake expanded for memory (except _stakeId) and compact for storage */
    struct StakeCache {
        uint40 _stakeId;
        uint256 _stakedSuns;
        uint256 _stakeShares;
        uint256 _lockedDay;
        uint256 _stakedDays;
        uint256 _unlockedDay;
    }
    struct StakeStore {
        uint40 stakeId;
        uint256 stakedSuns;
        uint256 stakeShares;
        uint16 lockedDay;
        uint16 stakedDays;
        uint16 unlockedDay;
    }
    struct UnstakeStore {
        uint40 stakeId;
        uint256 stakedSuns;
        uint256 stakeShares;
        uint16 lockedDay;
        uint16 stakedDays;
        uint16 unlockedDay;
        uint256 unstakePayout;
        uint256 unstakeDividends;
    }
    mapping(address => StakeStore[]) public stakeLists;
    mapping(address => UnstakeStore[]) public endedStakeLists;
    /* Temporary state for calculating daily rounds */
    struct DailyRoundState {
        uint256 _allocSupplyCached;
        uint256 _payoutTotal;
    }
    struct XfLobbyEntryStore {
        uint96 rawAmount;
        address referrerAddr;
    }
    struct XfLobbyQueueStore {
        uint40 headIndex;
        uint40 tailIndex;
        mapping(uint256 => XfLobbyEntryStore) entries;
    }
    mapping(uint256 => uint256) public xfLobby;
    mapping(uint256 => mapping(address => XfLobbyQueueStore))
        public xfLobbyMembers;
    mapping(address => uint256) public fromReferrs;
    mapping(uint256 => mapping(address => uint256))
        public jackpotReceivedAuction;

    /*  loteryLobbyEnter
     */
    event loteryLobbyEnter(
        uint256 timestamp,
        uint256 enterDay,
        uint256 indexed rawAmount
    );
    /*  loteryLobbyExit
     */
    event loteryLobbyExit(
        uint256 timestamp,
        uint256 enterDay,
        uint256 indexed rawAmount
    );
    event loteryWin(uint256 day, uint256 amount, address who);
    struct LoteryStore {
        uint256 change;
        uint256 chanceCount;
    }
    struct LoteryCount {
        address who;
        uint256 chanceCount;
    }
    struct winLoteryStat {
        address payable who;
        uint256 totalAmount;
        uint256 restAmount;
    }
    uint256 public lastEndedLoteryDay = 0;
    uint256 public lastEndedLoteryDayWithWinner = 0;
    uint256 public loteryDayWaitingForWinner = 0;
    uint256 public loteryDayWaitingForWinnerNew = 0;
    mapping(uint256 => winLoteryStat) public winners;
    mapping(uint256 => uint256) public dayChanceCount;
    // day => address => chance count
    mapping(uint256 => mapping(address => LoteryStore)) public loteryLobby;
    mapping(uint256 => LoteryCount[]) public loteryCount;

    /**
     * @dev PUBLIC FACING: Optionally update daily data for a smaller
     * range to reduce gas cost for a subsequent operation
     * @param beforeDay Only update days before this day number (optional; 0 for current day)
     */
    function dailyDataUpdate(uint256 beforeDay) external {
        GlobalsCache memory g;
        GlobalsCache memory gSnapshot;
        _globalsLoad(g, gSnapshot);
        /* Skip pre-claim period */
        require(g._currentDay > 1, "JACKPOT: Too early"); /* CLAIM_PHASE_START_DAY */
        if (beforeDay != 0) {
            require(
                beforeDay <= g._currentDay,
                "JACKPOT: beforeDay cannot be in the future"
            );
            _dailyDataUpdate(g, beforeDay);
        } else {
            /* Default to updating before current day */
            _dailyDataUpdate(g, g._currentDay);
        }
        _globalsSync(g, gSnapshot);
    }

    /**
     * @dev PUBLIC FACING: External helper to return multiple values of daily data with
     * a single call.
     * @param beginDay First day of data range
     * @param endDay Last day (non-inclusive) of data range
     * @return array of day stake shares total
     * @return array of day payout total
     */
    /* function dailyDataRange(uint256 beginDay, uint256 endDay)
        external
        view
        returns (uint256[] memory _dayStakeSharesTotal, uint256[] memory _dayPayoutTotal, uint256[] memory _dayDividends)
    {
        require(beginDay < endDay && endDay <= globals.dailyDataCount, "JACKPOT: range invalid");
        _dayStakeSharesTotal = new uint256[](endDay - beginDay);
        _dayPayoutTotal = new uint256[](endDay - beginDay);
        _dayDividends = new uint256[](endDay - beginDay);
        uint256 src = beginDay;
        uint256 dst = 0;
        do {
            _dayStakeSharesTotal[dst] = uint256(dailyData[src].dayStakeSharesTotal);
            _dayPayoutTotal[dst++] = uint256(dailyData[src].dayPayoutTotal);
            _dayDividends[dst++] = dailyData[src].dayDividends;
        } while (++src < endDay);
        return (_dayStakeSharesTotal, _dayPayoutTotal, _dayDividends);
    } */
    /**
     * @dev PUBLIC FACING: External helper to return most global info with a single call.
     * Ugly implementation due to limitations of the standard ABI encoder.
     * @return Fixed array of values
     */
    function globalInfo() external view returns (uint256[10] memory) {
        return [
            globals.lockedSunsTotal,
            globals.nextStakeSharesTotal,
            globals.shareRate,
            globals.stakePenaltyTotal,
            globals.dailyDataCount,
            globals.stakeSharesTotal,
            globals.latestStakeId,
            block.timestamp,
            totalSupply(),
            xfLobby[_currentDay()]
        ];
    }

    /**
     * @dev PUBLIC FACING: ERC20 totalSupply() is the circulating supply and does not include any
     * staked Suns. allocatedSupply() includes both.
     * @return Allocated Supply in Suns
     */
    function allocatedSupply() external view returns (uint256) {
        return totalSupply().add(globals.lockedSunsTotal);
    }

    /**
     * @dev PUBLIC FACING: External helper for the current day number since launch time
     * @return Current day number (zero-based)
     */
    function currentDay() external view returns (uint256) {
        return _currentDay();
    }

    function _currentDay() internal view returns (uint256) {
        return block.timestamp.sub(LAUNCH_TIME).div(ROUND_TIME);
    }

    function _dailyDataUpdateAuto(GlobalsCache memory g) internal {
        _dailyDataUpdate(g, g._currentDay);
    }

    function _globalsLoad(GlobalsCache memory g, GlobalsCache memory gSnapshot)
        internal
        view
    {
        g._lockedSunsTotal = globals.lockedSunsTotal;
        g._nextStakeSharesTotal = globals.nextStakeSharesTotal;
        g._shareRate = globals.shareRate;
        g._stakePenaltyTotal = globals.stakePenaltyTotal;
        g._dailyDataCount = globals.dailyDataCount;
        g._stakeSharesTotal = globals.stakeSharesTotal;
        g._latestStakeId = globals.latestStakeId;
        g._currentDay = _currentDay();
        _globalsCacheSnapshot(g, gSnapshot);
    }

    function _globalsCacheSnapshot(
        GlobalsCache memory g,
        GlobalsCache memory gSnapshot
    ) internal pure {
        gSnapshot._lockedSunsTotal = g._lockedSunsTotal;
        gSnapshot._nextStakeSharesTotal = g._nextStakeSharesTotal;
        gSnapshot._shareRate = g._shareRate;
        gSnapshot._stakePenaltyTotal = g._stakePenaltyTotal;
        gSnapshot._dailyDataCount = g._dailyDataCount;
        gSnapshot._stakeSharesTotal = g._stakeSharesTotal;
        gSnapshot._latestStakeId = g._latestStakeId;
    }

    function _globalsSync(GlobalsCache memory g, GlobalsCache memory gSnapshot)
        internal
    {
        if (
            g._lockedSunsTotal != gSnapshot._lockedSunsTotal ||
            g._nextStakeSharesTotal != gSnapshot._nextStakeSharesTotal ||
            g._shareRate != gSnapshot._shareRate ||
            g._stakePenaltyTotal != gSnapshot._stakePenaltyTotal
        ) {
            globals.lockedSunsTotal = g._lockedSunsTotal;
            globals.nextStakeSharesTotal = g._nextStakeSharesTotal;
            globals.shareRate = uint40(g._shareRate);
            globals.stakePenaltyTotal = g._stakePenaltyTotal;
        }
        if (
            g._dailyDataCount != gSnapshot._dailyDataCount ||
            g._stakeSharesTotal != gSnapshot._stakeSharesTotal ||
            g._latestStakeId != gSnapshot._latestStakeId
        ) {
            globals.dailyDataCount = uint16(g._dailyDataCount);
            globals.stakeSharesTotal = g._stakeSharesTotal;
            globals.latestStakeId = g._latestStakeId;
        }
    }

    function _stakeLoad(
        StakeStore storage stRef,
        uint40 stakeIdParam,
        StakeCache memory st
    ) internal view {
        /* Ensure caller's stakeIndex is still current */
        require(
            stakeIdParam == stRef.stakeId,
            "JACKPOT: stakeIdParam not in stake"
        );
        st._stakeId = stRef.stakeId;
        st._stakedSuns = stRef.stakedSuns;
        st._stakeShares = stRef.stakeShares;
        st._lockedDay = stRef.lockedDay;
        st._stakedDays = stRef.stakedDays;
        st._unlockedDay = stRef.unlockedDay;
    }

    function _stakeUpdate(StakeStore storage stRef, StakeCache memory st)
        internal
    {
        stRef.stakeId = st._stakeId;
        stRef.stakedSuns = st._stakedSuns;
        stRef.stakeShares = st._stakeShares;
        stRef.lockedDay = uint16(st._lockedDay);
        stRef.stakedDays = uint16(st._stakedDays);
        stRef.unlockedDay = uint16(st._unlockedDay);
    }

    function _stakeAdd(
        StakeStore[] storage stakeListRef,
        uint40 newStakeId,
        uint256 newStakedSuns,
        uint256 newStakeShares,
        uint256 newLockedDay,
        uint256 newStakedDays
    ) internal {
        stakeListRef.push(
            StakeStore(
                newStakeId,
                newStakedSuns,
                newStakeShares,
                uint16(newLockedDay),
                uint16(newStakedDays),
                uint16(0) // unlockedDay
            )
        );
    }

    /**
     * @dev Efficiently delete from an unordered array by moving the last element
     * to the "hole" and reducing the array length. Can change the order of the list
     * and invalidate previously held indexes.
     * @notice stakeListRef length and stakeIndex are already ensured valid in stakeEnd()
     * @param stakeListRef Reference to stakeLists[stakerAddr] array in storage
     * @param stakeIndex Index of the element to delete
     */
    function _stakeRemove(StakeStore[] storage stakeListRef, uint256 stakeIndex)
        internal
    {
        uint256 lastIndex = stakeListRef.length.sub(1);
        /* Skip the copy if element to be removed is already the last element */
        if (stakeIndex != lastIndex) {
            /* Copy last element to the requested element's "hole" */
            stakeListRef[stakeIndex] = stakeListRef[lastIndex];
        }
        /*
            Reduce the array length now that the array is contiguous.
            Surprisingly, 'pop()' uses less gas than 'stakeListRef.length = lastIndex'
        */
        stakeListRef.pop();
    }

    /**
     * @dev Estimate the stake payout for an incomplete day
     * @param g Cache of stored globals
     * @param stakeSharesParam Param from stake to calculate bonuses for
     * @return Payout in Suns
     */
    function _estimatePayoutRewardsDay(
        GlobalsCache memory g,
        uint256 stakeSharesParam
    ) internal view returns (uint256 payout) {
        /* Prevent updating state for this estimation */
        GlobalsCache memory gJpt;
        _globalsCacheSnapshot(g, gJpt);
        DailyRoundState memory rs;
        rs._allocSupplyCached = totalSupply().add(g._lockedSunsTotal);
        _dailyRoundCalc(gJpt, rs);
        /* Stake is no longer locked so it must be added to total as if it were */
        gJpt._stakeSharesTotal = gJpt._stakeSharesTotal.add(stakeSharesParam);
        payout = rs._payoutTotal.mul(stakeSharesParam).div(gJpt._stakeSharesTotal);
        return payout;
    }

    function _dailyRoundCalc(GlobalsCache memory g, DailyRoundState memory rs)
        private
        pure
    {
        /*
            Calculate payout round
            Inflation of 20% inflation per 365 days             (approx 1 year)
            dailyInterestRate   = exp(log(1 + 20%)  / 365) - 1
                                = exp(log(1 + 0.2) / 365) - 1
                                = exp(log(1.2) / 365) - 1
                                = 0.00049963589095561        (approx)
            payout  = allocSupply * dailyInterestRate
                    = allocSupply / (1 / dailyInterestRate)
                    = allocSupply / (1 / 0.00049963589095561)
                    = allocSupply / 2001.45749755364         (approx)
                    = allocSupply * 342345 / 685188967
        */
        //rs._payoutTotal = (rs._allocSupplyCached * 342345 / 685188967);
        rs._payoutTotal = rs._allocSupplyCached.mul(342345).div(685188967);
        if (g._stakePenaltyTotal != 0) {
            rs._payoutTotal = rs._payoutTotal.add(g._stakePenaltyTotal);
            g._stakePenaltyTotal = 0;
        }
    }

    function _dailyRoundCalcAndStore(
        GlobalsCache memory g,
        DailyRoundState memory rs,
        uint256 day
    ) private {
        _dailyRoundCalc(g, rs);
        dailyData[day].dayPayoutTotal = rs._payoutTotal;
        /* if (day == firstAuction + 2)
            dailyData[day].dayDividends = xfLobby[day] + xfLobby[firstAuction];
        if (day == firstAuction + 3)
            dailyData[day].dayDividends = xfLobby[day] + xfLobby[firstAuction + 1]; */
        dailyData[day].dayDividends = xfLobby[day];
        dailyData[day].dayStakeSharesTotal = g._stakeSharesTotal;
    }

    function _dailyDataUpdate(GlobalsCache memory g, uint256 beforeDay)
        private
    {
        if (g._dailyDataCount >= beforeDay) {
            /* Already up-to-date */
            return;
        }
        DailyRoundState memory rs;
        rs._allocSupplyCached = totalSupply().add(g._lockedSunsTotal);
        uint256 day = g._dailyDataCount;
        _dailyRoundCalcAndStore(g, rs, day);
        /* Stakes started during this day are added to the total the next day */
        if (g._nextStakeSharesTotal != 0) {
            g._stakeSharesTotal = g._stakeSharesTotal.add(g._nextStakeSharesTotal);
            g._nextStakeSharesTotal = 0;
        }
        while (++day < beforeDay) {
            _dailyRoundCalcAndStore(g, rs, day);
        }
        emit DailyDataUpdate(
            msg.sender,
            block.timestamp,
            g._dailyDataCount,
            day
        );
        g._dailyDataCount = day;
    }
}
function globalInfo() external view returns (uint256[10] memory) {
    return [
        globals.lockedSunsTotal,
        globals.nextStakeSharesTotal,
        globals.shareRate,
        globals.stakePenaltyTotal,
        globals.dailyDataCount,
        globals.stakeSharesTotal,
        globals.latestStakeId,
        block.timestamp,
        totalSupply(),
        xfLobby[_currentDay()]
    ];
}

/**
 * @dev PUBLIC FACING: External helper to return most global info with a single call.
 * Ugly implementation due to limitations of the standard ABI encoder.
 * @return Fixed array of values
 */