contract LotteryStorage is CoreUniLotterySettings
{
    // ==================== Structs & Constants ==================== //

    // Struct of holder data & scores.
    struct HolderData 
    {
        // --------- Slot --------- //

        // If this holder provided a valid referral ID, this is the 
        // address of a referrer - the user who generated the said
        // referral ID.
        address referrer;

        // Bonus score points, which can be given in certain events,
        // such as when player registers a valid referral ID.
        int16 bonusScore;

        // Number of all child referrees, including multi-level ones.
        // Updated by traversing child->parent way, incrementing
        // every node's counter by one.
        // Used in Winner Selection Algorithm, to determine how much
        // to divide the accumulated referree scores by.
        uint16 referreeCount;


        // --------- Slot --------- //

        // If this holder has generated his own referral ID, this is
        // that ID. If he hasn't generated an ID, this is zero.
        uint256 referralID;


        // --------- Slot --------- //

        // The intermediate individual score factor variables.
        // Ether contributed: ( buys - sells ). Can be negative.
        int40 etherContributed;

        // Time x ether factor: (relativeTxTime * etherAmount).
        int40 timeFactors;

        // Token balance score factor of this holder - we use int,
        // for easier computation of player scores in our algorithms.
        int40 tokenBalance;

        // Accumulated referree score factors - ether contributed by
        // all referrees, time factors, and token balances of all
        // referrees.
        int40 referree_etherContributed;
        int40 referree_timeFactors;
        int40 referree_tokenBalance;
    }


    // Final Score (end lottery score * randomValue) structure.
    struct FinalScore 
    {
        address addr;           // 20 bytes \
        uint16 holderIndex;     // 2 bytes  | = 30 bytes => 1 slot.
        uint64 score;           // 8 bytes  /
    }


    // Winner Indexes structure - used to efficiently store Winner
    // indexes in holder's array, after completing the Winner Selection
    // Algorithm.
    // To save Space, we store these in a struct, with uint16 array
    // with 16 items - so this struct takes up excactly 1 slot.
    struct WinnerIndexStruct
    {
        uint16[ 16 ] indexes;
    }


    // A structure which is used by Winner Selection algorithm,
    // which is a subset of the LotteryConfig structure, containing
    // only items necessary for executing the Winner Selection algorigm.
    // More detailed member description can be found in LotteryConfig
    // structure description.
    // Takes up only one slot!
    struct WinnerAlgorithmConfig
    {
        // --------- Slot --------- //

        // Individual player max score parts.
        int16 maxPlayerScore_etherContributed;
        int16 maxPlayerScore_tokenHoldingAmount;
        int16 maxPlayerScore_timeFactor;
        int16 maxPlayerScore_refferalBonus;

        // Number of lottery winners.
        uint16 winnerCount;

        // Score-To-Random ration data (as a rational ratio number).
        // For example if 1:5, then scorePart = 1, and randPart = 5.
        uint16 randRatio_scorePart;
        uint16 randRatio_randPart;

        // The Ending Algorithm type.
        uint8 endingAlgoType;
    }


    // Structure containing the minimum and maximum values of
    // holder intermediate scores.
    // These values get updated on transfers during ACTIVE stage,
    // when holders buy/sell tokens.
    //
    // Used in winner selection algorithm, to normalize the scores in
    // a single loop, to avoid looping additional time to find min/max.
    //
    // Structure takes up only a single slot!
    //
    struct MinMaxHolderScores
    {
        // --------- Slot --------- //

        int40 holderScore_etherContributed_min;
        int40 holderScore_etherContributed_max;

        int40 holderScore_timeFactors_min;
        int40 holderScore_timeFactors_max;

        int40 holderScore_tokenBalance_min;
        int40 holderScore_tokenBalance_max;
    }

    // Referral score variant of the structure above.
    // Also, only a single slot!
    //
    struct MinMaxReferralScores
    {
        // --------- Slot --------- //

        // Min&Max values for referrer scores.
        int40 referralScore_etherContributed_min;
        int40 referralScore_etherContributed_max;

        int40 referralScore_timeFactors_min;
        int40 referralScore_timeFactors_max;

        int40 referralScore_tokenBalance_min;
        int40 referralScore_tokenBalance_max;
    }

    // ROOT_REFERRER constant.
    // Used to prevent cyclic dependencies on referral tree.
    address constant ROOT_REFERRER = address( 1 );

    // Max referral tree depth - maximum number of referrees that
    // a referrer can get.
    uint constant MAX_REFERRAL_DEPTH = 10;

    // Precision of division operations.
    int constant PRECISION = 10000;

    // Random number modulo to use when obtaining random numbers from
    // the random seed + nonce, using keccak256.
    // This is the maximum available Score Random Factor, plus one.
    // By default, 10^9 (one billion).
    //
    uint constant RANDOM_MODULO = (10 ** 9);

    // Maximum number of holders that the MinedWinnerSelection algorithm
    // can process. Related to block gas limit.
    uint constant MINEDSELECTION_MAX_NUMBER_OF_HOLDERS = 300;

    // Maximum number of holders that the WinnerSelfValidation algorithm
    // can process. Related to block gas limit.
    uint constant SELFVALIDATION_MAX_NUMBER_OF_HOLDERS = 1200;


    // ==================== State Variables ==================== //

    // --------- Slot --------- //

    // The Lottery address that this storage belongs to.
    // Is set by the "initialize()", called by corresponding Lottery.
    address lottery;

    // The Random Seed, that was passed to us from Randomness Provider,
    // or generated alternatively.
    uint64 randomSeed;

    // The actual number of winners that there will be. Set after
    // completing the Winner Selection Algorithm.
    uint16 numberOfWinners;

    // Bool indicating if Winner Selection Algorithm has been executed.
    bool algorithmCompleted;


    // --------- Slot --------- //

    // Winner Algorithm config. Specified in Initialization().
    WinnerAlgorithmConfig algConfig;

    // --------- Slot --------- //

    // The Min-Max holder score storage.
    MinMaxHolderScores public minMaxScores;
    MinMaxReferralScores public minMaxReferralScores;

    // --------- Slot --------- //

    // Array of holders.
    address[] public holders;

    // --------- Slot --------- //

    // Holder array indexes mapping, for O(1) array element access.
    mapping( address => uint ) holderIndexes;

    // --------- Slot --------- //

    // Mapping of holder data.
    mapping( address => HolderData ) public holderData;

    // --------- Slot --------- //

    // Mapping of referral IDs to addresses of holders who generated
    // those IDs.
    mapping( uint256 => address ) referrers;

    // --------- Slot --------- //

    // The array of final-sorted winners (set after Winner Selection
    // Algorithm completes), that contains the winners' indexes
    // in the "holders" array, to save space.
    //
    // Notice that by using uint16, we can fit 16 items into one slot!
    // So, if there are 160 winners, we only take up 10 slots, so
    // only 20,000 * 10 = 200,000 gas gets consumed!
    //
    WinnerIndexStruct[] sortedWinnerIndexes;



    // ==============       Internal (Private) Functions    ============== //

    // Lottery-Only modifier.
    modifier lotteryOnly
    {
        require( msg.sender == address( lottery )/*,
                 "Function can only be called by Lottery that this"
                 "Storage Contract belongs to!" */);
        _;
    }


    // ============== [ BEGIN ] LOTTERY QUICKSORT FUNCTIONS ============== //

    /**
     *  QuickSort and QuickSelect algorithm functionality code.
     *
     *  These algorithms are used to find the lottery winners in
     *  an array of final random-factored scores.
     *  As the highest-scorers win, we need to sort an array to
     *  identify them.
     *
     *  For this task, we use QuickSelect to partition array into
     *  winner part (elements with score larger than X, where X is
     *  n-th largest element, where n is number of winners),
     *  and others (non-winners), who are ignored to save computation
     *  power.
     *  Then we sort the winner part only, using QuickSort, and
     *  distribute prizes to winners accordingly.
     */

    // Swap function used in QuickSort algorithms.
    //
    function QSort_swap( FinalScore[] memory list, 
                         uint a, uint b )               
                                                        internal pure
    {
        FinalScore memory tmp = list[ a ];
        list[ a ] = list[ b ];
        list[ b ] = tmp;
    }

    // Standard Hoare's partition scheme function, used for both
    // QuickSort and QuickSelect.
    //
    function QSort_partition( 
            FinalScore[] memory list, 
            int lo, int hi )
                                                        internal pure
    returns( int newPivotIndex )
    {
        uint64 pivot = list[ uint( hi + lo ) / 2 ].score;
        int i = lo - 1;
        int j = hi + 1;

        while( true ) 
        {
            do {
                i++;
            } while( list[ uint( i ) ].score > pivot ) ;

            do {
                j--;
            } while( list[ uint( j ) ].score < pivot ) ;

            if( i >= j )
                return j;

            QSort_swap( list, uint( i ), uint( j ) );
        }
    }

    // QuickSelect's Lomuto partition scheme.
    //
    function QSort_LomutoPartition(
            FinalScore[] memory list,
            uint left, uint right, uint pivotIndex )
                                                        internal pure
    returns( uint newPivotIndex )
    {
        uint pivotValue = list[ pivotIndex ].score;
        QSort_swap( list, pivotIndex, right );  // Move pivot to end
        uint storeIndex = left;
        
        for( uint i = left; i < right; i++ )
        {
            if( list[ i ].score > pivotValue ) {
                QSort_swap( list, storeIndex, i );
                storeIndex++;
            }
        }

        // Move pivot to its final place, and return the pivot's index.
        QSort_swap( list, right, storeIndex );
        return storeIndex;
    }

    // QuickSelect algorithm (iterative).
    //
    function QSort_QuickSelect(
            FinalScore[] memory list,
            int left, int right, int k )
                                                        internal pure
    returns( int indexOfK )
    {
        while( true ) {
            if( left == right )
                return left;

            int pivotIndex = int( QSort_LomutoPartition( list, 
                    uint(left), uint(right), uint(right) ) );

            if( k == pivotIndex )
                return k;
            else if( k < pivotIndex )
                right = pivotIndex - 1;
            else
                left = pivotIndex + 1;
        }
    }

    // Standard QuickSort function.
    //
    function QSort_QuickSort(
            FinalScore[] memory list,
            int lo, int hi )
                                                        internal pure
    {
        if( lo < hi ) {
            int p = QSort_partition( list, lo, hi );
            QSort_QuickSort( list, lo, p );
            QSort_QuickSort( list, p + 1, hi );
        }
    }

    // ============== [ END ]   LOTTERY QUICKSORT FUNCTIONS ============== //

    // ------------ Ending Stage - Winner Selection Algorithm ------------ //

    /**
     *  Compute the individual player score factors for a holder.
     *  Function split from the below one (ending_Stage_2), to avoid
     *  "Stack too Deep" errors.
     */
    function computeHolderIndividualScores( 
            WinnerAlgorithmConfig memory cfg,
            MinMaxHolderScores memory minMax,
            HolderData memory hdata )
                                                        internal pure
    returns( int individualScore )
    {
        // Normalize the scores, by subtracting minimum and dividing
        // by maximum, to get the score values specified in cfg.
        // Use precision of 100, then round.
        //
        // Notice that we're using int arithmetics, so division 
        // truncates. That's why we use PRECISION, to simulate
        // rounding.
        //
        // This formula is better explained in example.
        // In this example, we use variable abbreviations defined
        // below, on formula's right side comments.
        //
        // Say, values are these in our example:
        // e = 4, eMin = 1, eMax = 8, MS = 5, P = 10.
        //
        // So, let's calculate the score using the formula:
        // ( ( ( (4 - 1) * 10 * 5 ) / (8 - 1) ) + (10 / 2) ) / 10 =
        // ( ( (    3    * 10 * 5 ) /    7    ) +     5    ) / 10 =
        // ( (         150          /    7    ) +     5    ) / 10 =
        // ( (         150          /    7    ) +     5    ) / 10 =
        // (                    20              +     5    ) / 10 =
        //                          25                       / 10 =
        //                        [ 2.5 ]                         = 2
        //
        // So, with truncation, we see that for e = 4, the score
        // is 2 out of 5 maximum.
        // That's because the minimum ether contributed was 1, and
        // maximum was 8.
        // So, 4 stays below the middle, and gets a nicely rounded 
        // score of 2.

        // Compute etherContributed.
        int score_etherContributed = ( (
            ( int( hdata.etherContributed -                      // e
                   minMax.holderScore_etherContributed_min )     // eMin
              * PRECISION * cfg.maxPlayerScore_etherContributed )// P * MS
            / int( minMax.holderScore_etherContributed_max -     // eMax
                   minMax.holderScore_etherContributed_min )     // eMin
        ) + (PRECISION / 2) ) / PRECISION;

        // Compute timeFactors.
        int score_timeFactors = ( (
            ( int( hdata.timeFactors -                          // e
                   minMax.holderScore_timeFactors_min )         // eMin
              * PRECISION * cfg.maxPlayerScore_timeFactor )     // P * MS
            / int( minMax.holderScore_timeFactors_max -         // eMax
                   minMax.holderScore_timeFactors_min )         // eMin
        ) + (PRECISION / 2) ) / PRECISION;

        // Compute tokenBalance.
        int score_tokenBalance = ( (
            ( int( hdata.tokenBalance -                         // e
                   minMax.holderScore_tokenBalance_min )        // eMin
              * PRECISION * cfg.maxPlayerScore_tokenHoldingAmount )
            / int( minMax.holderScore_tokenBalance_max -        // eMax
                   minMax.holderScore_tokenBalance_min )        // eMin
        ) + (PRECISION / 2) ) / PRECISION;

        // Return the accumulated individual score (excluding referrees).
        return score_etherContributed + score_timeFactors +
               score_tokenBalance;
    }


    /**
     *  Compute the unified Referree-Score of a player, who's got
     *  the accumulated factor-scores of all his referrees in his 
     *  holderData structure.
     *
     *  @param individualToReferralRatio - an int value, computed 
     *      before starting the winner score computation loop, in 
     *      the ending_Stage_2 initial part, to save computation
     *      time later.
     *      This is the ratio of the maximum available referral score,
     *      to the maximum available individual score, as defined in
     *      the config (for example, if max.ref.score is 20, and 
     *      max.ind.score is 40, then the ratio is 20/40 = 0.5).
     *      
     *      We use this ratio to transform the computed accumulated
     *      referree individual scores to the standard referrer's
     *      score, by multiplying by that ratio.
     */
    function computeReferreeScoresForHolder( 
            int individualToReferralRatio,
            WinnerAlgorithmConfig memory cfg,
            MinMaxReferralScores memory minMax,
            HolderData memory hdata )
                                                        internal pure
    returns( int unifiedReferreeScore )
    {
        // If number of referrees of this HODLer is Zero, then
        // his referree score is also zero.
        if( hdata.referreeCount == 0 )
            return 0;

        // Now, compute the Referree's Accumulated Scores.
        //
        // Here we use the same formula as when computing individual
        // scores (in the function above), but we use referree parts
        // instead.

        // Compute etherContributed.
        int referreeScore_etherContributed = ( (
            ( int( hdata.referree_etherContributed -
                   minMax.referralScore_etherContributed_min )
              * PRECISION * cfg.maxPlayerScore_etherContributed )
            / int( minMax.referralScore_etherContributed_max -
                   minMax.referralScore_etherContributed_min )
        ) );

        // Compute timeFactors.
        int referreeScore_timeFactors = ( (
            ( int( hdata.referree_timeFactors -
                   minMax.referralScore_timeFactors_min )
              * PRECISION * cfg.maxPlayerScore_timeFactor )
            / int( minMax.referralScore_timeFactors_max -
                   minMax.referralScore_timeFactors_min )
        ) );

        // Compute tokenBalance.
        int referreeScore_tokenBalance = ( (
            ( int( hdata.referree_tokenBalance -
                   minMax.referralScore_tokenBalance_min )
              * PRECISION * cfg.maxPlayerScore_tokenHoldingAmount )
            / int( minMax.referralScore_tokenBalance_max -
                   minMax.referralScore_tokenBalance_min )
        ) );


        // Accumulate 'em all !
        // Then, multiply it by the ratio of all individual max scores
        // (maxPlayerScore_etherContributed, timeFactor, tokenBalance),
        // to the maxPlayerScore_refferalBonus.
        // Use the same precision.
        unifiedReferreeScore = int( ( (
                ( ( referreeScore_etherContributed +
                    referreeScore_timeFactors +
                    referreeScore_tokenBalance ) + (PRECISION / 2)
                ) / PRECISION
            ) * individualToReferralRatio
        ) / PRECISION );
    }


    /**
     *  Update Min & Max values for individual holder scores.
     */
    function priv_updateMinMaxScores_individual(
            MinMaxHolderScores memory minMax,
            int40 _etherContributed,
            int40 _timeFactors,
            int40 _tokenBalance )
                                                    internal
                                                    pure
    {
        // etherContributed:
        if( _etherContributed > 
            minMax.holderScore_etherContributed_max )
            minMax.holderScore_etherContributed_max = 
                _etherContributed;

        if( _etherContributed <
            minMax.holderScore_etherContributed_min )
            minMax.holderScore_etherContributed_min = 
                _etherContributed;

        // timeFactors:
        if( _timeFactors > 
            minMax.holderScore_timeFactors_max )
            minMax.holderScore_timeFactors_max = 
                _timeFactors;

        if( _timeFactors <
            minMax.holderScore_timeFactors_min )
            minMax.holderScore_timeFactors_min = 
                _timeFactors;

        // tokenBalance:
        if( _tokenBalance > 
            minMax.holderScore_tokenBalance_max )
            minMax.holderScore_tokenBalance_max = 
                _tokenBalance;

        if( _tokenBalance <
            minMax.holderScore_tokenBalance_min )
            minMax.holderScore_tokenBalance_min = 
                _tokenBalance;
    }


    /**
     *  Update Min & Max values for referral scores.
     */
    function priv_updateMinMaxScores_referral(
            MinMaxReferralScores memory minMax,
            int40 _etherContributed,
            int40 _timeFactors,
            int40 _tokenBalance )
                                                    internal
                                                    pure
    {
        // etherContributed:
        if( _etherContributed > 
            minMax.referralScore_etherContributed_max )
            minMax.referralScore_etherContributed_max = 
                _etherContributed;

        if( _etherContributed <
            minMax.referralScore_etherContributed_min )
            minMax.referralScore_etherContributed_min = 
                _etherContributed;

        // timeFactors:
        if( _timeFactors > 
            minMax.referralScore_timeFactors_max )
            minMax.referralScore_timeFactors_max = 
                _timeFactors;

        if( _timeFactors <
            minMax.referralScore_timeFactors_min )
            minMax.referralScore_timeFactors_min = 
                _timeFactors;

        // tokenBalance:
        if( _tokenBalance > 
            minMax.referralScore_tokenBalance_max )
            minMax.referralScore_tokenBalance_max = 
                _tokenBalance;

        if( _tokenBalance <
            minMax.referralScore_tokenBalance_min )
            minMax.referralScore_tokenBalance_min = 
                _tokenBalance;
    }



    // =================== PUBLIC FUNCTIONS =================== //

    /**
     *  Update current holder's score with given change values, and
     *  Propagate the holder's current transfer's score changes
     *  through the referral chain, updating every parent referrer's
     *  accumulated referree scores, until the ROOT_REFERRER or zero
     *  address referrer is encountered.
     */
    function updateAndPropagateScoreChanges(
            address holder,
            int __etherContributed_change,
            int __timeFactors_change,
            int __tokenBalance_change )
                                                        public
                                                        lotteryOnly
    {
        // Convert the data into shrinked format - leave only
        // 4 decimals of Ether precision, and drop the decimal part
        // of ULT tokens absolutely.
        // Don't change TimeFactors, as it is already adjusted in
        // Lottery contract's code.
        int40 timeFactors_change = int40( __timeFactors_change );

        int40 etherContributed_change = int40(
            __etherContributed_change / int(1 ether / 10000) );
 
        int40 tokenBalance_change = int40(
            __tokenBalance_change / int(1 ether) );

        // Update current holder's score.
        holderData[ holder ].etherContributed += etherContributed_change;
        holderData[ holder ].timeFactors += timeFactors_change;
        holderData[ holder ].tokenBalance += tokenBalance_change;

        // Check if scores are exceeding current min/max scores, 
        // and if so, update the min/max scores.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_updateMinMaxScores_individual(
            minMaxCpy,
            holderData[ holder ].etherContributed,
            holderData[ holder ].timeFactors,
            holderData[ holder ].tokenBalance
        );

        // Propagate the score through the referral chain.
        // Dive at maximum to the depth of 10, to avoid "Outta Gas"
        // errors.
        uint depth = 0;
        address referrerAddr = holderData[ holder ].referrer;

        while( referrerAddr != ROOT_REFERRER && 
               referrerAddr != address( 0 )  &&
               depth < MAX_REFERRAL_DEPTH )
        {
            // Update this referrer's accumulated referree scores.
            holderData[ referrerAddr ].referree_etherContributed +=
                etherContributed_change;

            holderData[ referrerAddr ].referree_timeFactors +=
                timeFactors_change;

            holderData[ referrerAddr ].referree_tokenBalance +=
                tokenBalance_change;

            // Update MinMax according to this referrer's score.
            priv_updateMinMaxScores_referral(
                minMaxRefCpy,
                holderData[ referrerAddr ].referree_etherContributed,
                holderData[ referrerAddr ].referree_timeFactors,
                holderData[ referrerAddr ].referree_tokenBalance
            );

            // Move to the higher-level referrer.
            referrerAddr = holderData[ referrerAddr ].referrer;
            depth++;
        }

        // Check if MinMax have changed. If so, update it.
        if( keccak256( abi.encode( minMaxCpy ) ) != 
            keccak256( abi.encode( minMaxScores ) ) )
            minMaxScores = minMaxCpy;

        // Check referral part.
        if( keccak256( abi.encode( minMaxRefCpy ) ) != 
            keccak256( abi.encode( minMaxReferralScores ) ) )
            minMaxReferralScores = minMaxRefCpy;
    }


    /**
     *  Pure function to fix an in-memory copy of MinMaxScores,
     *  by changing equal min-max pairs to differ by one.
     *  This is needed to avoid division-by-zero in some calculations.
     */
    function priv_fixMinMaxIfEqual(
            MinMaxHolderScores memory minMaxCpy,
            MinMaxReferralScores memory minMaxRefCpy )
                                                            internal
                                                            pure
    {
        // Individual part
        if( minMaxCpy.holderScore_etherContributed_min ==
            minMaxCpy.holderScore_etherContributed_max )
            minMaxCpy.holderScore_etherContributed_max =
            minMaxCpy.holderScore_etherContributed_min + 1;

        if( minMaxCpy.holderScore_timeFactors_min ==
            minMaxCpy.holderScore_timeFactors_max )
            minMaxCpy.holderScore_timeFactors_max =
            minMaxCpy.holderScore_timeFactors_min + 1;

        if( minMaxCpy.holderScore_tokenBalance_min ==
            minMaxCpy.holderScore_tokenBalance_max )
            minMaxCpy.holderScore_tokenBalance_max =
            minMaxCpy.holderScore_tokenBalance_min + 1;

        // Referral part
        if( minMaxRefCpy.referralScore_etherContributed_min ==
            minMaxRefCpy.referralScore_etherContributed_max )
            minMaxRefCpy.referralScore_etherContributed_max =
            minMaxRefCpy.referralScore_etherContributed_min + 1;

        if( minMaxRefCpy.referralScore_timeFactors_min ==
            minMaxRefCpy.referralScore_timeFactors_max )
            minMaxRefCpy.referralScore_timeFactors_max =
            minMaxRefCpy.referralScore_timeFactors_min + 1;

        if( minMaxRefCpy.referralScore_tokenBalance_min ==
            minMaxRefCpy.referralScore_tokenBalance_max )
            minMaxRefCpy.referralScore_tokenBalance_max =
            minMaxRefCpy.referralScore_tokenBalance_min + 1;
    }


    /** 
     *  Function executes the Lottery Winner Selection Algorithm,
     *  and writes the final, sorted array, containing winner rankings.
     *
     *  This function is called from the Lottery's Mining Stage Step 2,
     *
     *  This is the final function that lottery performs actively - 
     *  and arguably the most important - because it determines 
     *  lottery winners through Winner Selection Algorithm.
     *
     *  The random seed must be already set, before calling this function.
     */
    function executeWinnerSelectionAlgorithm()
                                                        public
                                                        lotteryOnly
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Can only be performed if algorithm is MinedWinnerSelection!
        require( cfg.endingAlgoType ==
                 uint8(Lottery.EndingAlgoType.MinedWinnerSelection)/*,
                 "Algorithm cannot be performed on current Algo-Type!" */);

        // Now, we gotta find the winners using a Randomized Score-Based
        // Winner Selection Algorithm.
        //
        // During transfers, all player intermediate scores 
        // (etherContributed, timeFactors, and tokenBalances) were
        // already set in every holder's HolderData structure,
        // during operations of updateHolderData_preTransfer() function.
        //
        // Minimum and maximum values are also known, so normalization
        // will be easy.
        // All referral tree score data were also properly propagated
        // during operations of updateAndPropagateScoreChanges() function.
        //
        // All we block.timestamp have to do, is loop through holder array, and
        // compute randomized final scores for every holder, into
        // the Final Score array.

        // Declare the Final Score array - computed for all holders.
        uint ARRLEN = 
            ( holders.length > MINEDSELECTION_MAX_NUMBER_OF_HOLDERS ?
              MINEDSELECTION_MAX_NUMBER_OF_HOLDERS : holders.length );

        FinalScore[] memory finalScores = new FinalScore[] ( ARRLEN );

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );


        // Random Factor of scores, to maintain random-to-determined
        // ratio equal to specific value (1:5 for example - 
        // "randPart" == 5/*, "scorePart" */== 1).
        //
        // maxAvailablePlayerScore * FACT   ---   scorePart
        // RANDOM_MODULO                    ---   randPart
        //
        //                                  RANDOM_MODULO * scorePart
        // maxAvailablePlayerScore * FACT = -------------------------
        //                                          randPart
        //
        //              RANDOM_MODULO * scorePart
        // FACT = --------------------------------------
        //          randPart * maxAvailablePlayerScore

        int SCORE_RAND_FACT =
            ( PRECISION * int(RANDOM_MODULO * cfg.randRatio_scorePart) ) /
            ( int(cfg.randRatio_randPart) * maxAvailablePlayerScore );


        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // Loop through all the holders.
        for( uint i = 0; i < ARRLEN; i++ )
        {
            // Fetch the needed holder data to in-memory hdata variable,
            // to save gas on score part computing functions.
            HolderData memory hdata;

            // Slot 1:
            hdata.etherContributed =
                holderData[ holders[ i ] ].etherContributed;
            hdata.timeFactors =
                holderData[ holders[ i ] ].timeFactors;
            hdata.tokenBalance =
                holderData[ holders[ i ] ].tokenBalance;
            hdata.referreeCount =
                holderData[ holders[ i ] ].referreeCount;

            // Slot 2:
            hdata.referree_etherContributed =
                holderData[ holders[ i ] ].referree_etherContributed;
            hdata.referree_timeFactors =
                holderData[ holders[ i ] ].referree_timeFactors;
            hdata.referree_tokenBalance =
                holderData[ holders[ i ] ].referree_tokenBalance;
            hdata.bonusScore =
                holderData[ holders[ i ] ].bonusScore;


            // Now, add bonus score, and compute total player's score:
            // Bonus part, individual score part, and referree score part.
            int totalPlayerScore = 
                    hdata.bonusScore
                    +
                    computeHolderIndividualScores(
                        cfg, minMaxCpy, hdata )
                    +
                    computeReferreeScoresForHolder( 
                        individualToReferralRatio, cfg, 
                        minMaxRefCpy, hdata );


            // Check if total player score <= 0. If so, make it equal
            // to 1, because otherwise randomization won't be possible.
            if( totalPlayerScore <= 0 )
                totalPlayerScore = 1;

            // Now, check if it's not more than max! If so, lowerify.
            // This could have happen'd because of bonus.
            if( totalPlayerScore > maxAvailablePlayerScore )
                totalPlayerScore = maxAvailablePlayerScore;


            // Multiply the score by the Random Modulo Adjustment
            // Factor, to get fairer ratio of random-to-determined data.
            totalPlayerScore =  ( totalPlayerScore * SCORE_RAND_FACT ) /
                                ( PRECISION );

            // Score is computed!
            // Now, randomize it, and add to Final Scores Array.
            // We use keccak to generate a random number from random seed,
            // using holder's address as a nonce.

            uint modulizedRandomNumber = uint(
                keccak256( abi.encodePacked( randomSeed, holders[ i ] ) )
            ) % RANDOM_MODULO;

            // Add the random number, to introduce the random factor.
            // Ratio of (current) totalPlayerScore to modulizedRandomNumber
            // is the same as ratio of randRatio_scorePart to 
            // randRatio_randPart.

            uint endScore = uint( totalPlayerScore ) + modulizedRandomNumber;

            // Finally, set this holder's final score data.
            finalScores[ i ].addr = holders[ i ];
            finalScores[ i ].holderIndex = uint16( i );
            finalScores[ i ].score = uint64( endScore );
        }

        // All final scores are block.timestamp computed.
        // Sort the array, to find out the highest scores!

        // Firstly, partition an array to only work on top K scores,
        // where K is the number of winners.
        // There can be a rare case where specified number of winners is
        // more than lottery token holders. We got that covered.

        require( finalScores.length > 0 );

        uint K = cfg.winnerCount - 1;
        if( K > finalScores.length-1 )
            K = finalScores.length-1;   // Must be THE LAST ELEMENT's INDEX.

        // Use QuickSelect to do this.
        QSort_QuickSelect( finalScores, 0, 
            int( finalScores.length - 1 ), int( K ) );

        // Now, QuickSort only the first K items, because the rest
        // item scores are not high enough to become winners.
        QSort_QuickSort( finalScores, 0, int( K ) );

        // Now, the winner array is sorted, with the highest scores
        // sitting at the first positions!
        // Let's set up the winner indexes array, where we'll store
        // the winners' indexes in the holders array.
        // So, if this array is [8, 2, 3], that means that
        // Winner #1 is holders[8], winner #2 is holders[2], and
        // winner #3 is holders[3].

        // Set the Number Of Winners variable.
        numberOfWinners = uint16( K + 1 );

        // Now, we can loop through the first numberOfWinners elements, to set
        // the holder indexes!
        // Loop through 16 elements at a time, to fill the structs.
        for( uint offset = 0; offset < numberOfWinners; offset += 16 )
        {
            WinnerIndexStruct memory windStruct;
            uint loopStop = ( offset + 16 > numberOfWinners ?
                              numberOfWinners : offset + 16 );

            for( uint i = offset; i < loopStop; i++ )
            {
                windStruct.indexes[ i - offset ] =finalScores[ i ].holderIndex;
            }

            // Push this block.timestamp-filled struct to the storage array!
            sortedWinnerIndexes.push( windStruct );
        }

        // That's it! We're done!
        algorithmCompleted = true;
    }


    /**
     *  Add a holder to holders array.
     *  @param holder   - address of a holder to add.
     */
    function addHolder( address holder )
                                                        public
                                                        lotteryOnly
    {
        // Add it to list, and set index in the mapping.
        holders.push( holder );
        holderIndexes[ holder ] = holders.length - 1;
    }

    /**
     *  Removes the holder 'sender' from the Holders Array.
     *  However, this holder's HolderData structure persists!
     *
     *  Notice that no index validity checks are performed, so, if
     *  'sender' is not present in "holderIndexes" mapping, this
     *  function will remove the 0th holder instead!
     *  This is not a problem for us, because Lottery calls this
     *  function only when it's absolutely certain that 'sender' is
     *  present in the holders array.
     *
     *  @param sender   - address of a holder to remove.
     *      Named 'sender', because when token sender sends away all
     *      his tokens, he must then be removed from holders array.
     */
    function removeHolder( address sender )
                                                        public
                                                        lotteryOnly
    {
        // Get index of the sender address in the holders array.
        uint index = holderIndexes[ sender ];

        // Remove the sender from array, by copying last element's
        // value into the index'th element, where sender was before.
        holders[ index ] = holders[ holders.length - 1 ];

        // Remove the last element of array, which we've just copied.
        holders.pop();

        // Update indexes: remove the sender's index from the mapping,
        // and change the previoulsy-last element's index to the
        // one where we copied it - where sender was before.
        delete holderIndexes[ sender ];
        holderIndexes[ holders[ index ] ] = index;
    }


    /**
     *  Get holder array length.
     */
    function getHolderCount()
                                                    public view
    returns( uint )
    {
        return holders.length;
    }


    /**
     *  Generate a referral ID for a token holder.
     *  Referral ID is used to refer other wallets into playing our
     *  lottery.
     *  - Referrer gets bonus points for every wallet that bought 
     *    lottery tokens and specified his referral ID.
     *  - Referrees (wallets who got referred by registering a valid
     *    referral ID, corresponding to some referrer), get some
     *    bonus points for specifying (registering) a referral ID.
     *
     *  Referral ID is a uint256 number, which is generated by
     *  keccak256'ing the holder's address, holder's current
     *  token ballance, and current time.
     */
    function generateReferralID( address holder )
                                                            public
                                                            lotteryOnly
    returns( uint256 referralID )
    {
        // Check if holder has some tokens, and doesn't
        // have his own referral ID yet.
        require( holderData[ holder ].tokenBalance != 0/*,
                 "holder doesn't have any lottery tokens!" */);

        require( holderData[ holder ].referralID == 0/*,
                 "Holder already has a referral ID!" */);

        // Generate a referral ID with keccak.
        uint256 refID = uint256( keccak256( abi.encodePacked( 
                holder, holderData[ holder ].tokenBalance, block.timestamp ) ) );

        // Specify the ID as current ID of this holder.
        holderData[ holder ].referralID = refID;

        // If this holder wasn't referred by anyone (his referrer is
        // not set), and he's block.timestamp generated his own ID, he won't
        // be able to register as a referree of someone else 
        // from block.timestamp on.
        // This is done to prevent circular dependency in referrals.
        // Do it by setting a referrer to ROOT_REFERRER address,
        // which is an invalid address (address(1)).
        if( holderData[ holder ].referrer == address( 0 ) )
            holderData[ holder ].referrer = ROOT_REFERRER;

        // Create a new referrer with this ID.
        referrers[ refID ] = holder;
        
        return refID;
    }


    /**
     *  Register a referral for a token holder, using a valid
     *  referral ID got from a referrer.
     *  This function is called by a referree, who obtained a
     *  valid referral ID from some referrer, who previously
     *  generated it using generateReferralID().
     *
     *  You can only register a referral once!
     *  When you do so, you get bonus referral points!
     */
    function registerReferral(
            address holder,
            int16 referralRegisteringBonus,
            uint256 referralID )
                                                            public
                                                            lotteryOnly
    returns( address _referrerAddress )
    {
        // Check if this holder has some tokens, and if he hasn't
        // registered a referral yet.
        require( holderData[ holder ].tokenBalance != 0/*,
                 "holder doesn't have any lottery tokens!" */);

        require( holderData[ holder ].referrer == address( 0 )/*,
                 "holder already has registered a referral!" */);

        // Create a local memory copy of minMaxReferralScores.
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        // Get the referrer's address from his ID, and specify
        // it as a referrer of holder.
        holderData[ holder ].referrer = referrers[ referralID ];

        // Bonus points are added to this holder's score for
        // registering a referral!
        holderData[ holder ].bonusScore = referralRegisteringBonus;

        // Increment number of referrees for every parent referrer,
        // by traversing a referral tree child->parent way.
        address referrerAddr = holderData[ holder ].referrer;

        // Set the return value.
        _referrerAddress = referrerAddr;

        // Traverse a tree.
        while( referrerAddr != ROOT_REFERRER && 
               referrerAddr != address( 0 ) )
        {
            // Increment referree count for this referrrer.
            holderData[ referrerAddr ].referreeCount++;

            // Update the Referrer Scores of the referrer, adding this
            // referree's scores to it's current values.
            holderData[ referrerAddr ].referree_etherContributed +=
                holderData[ holder ].etherContributed;

            holderData[ referrerAddr ].referree_timeFactors +=
                holderData[ holder ].timeFactors;

            holderData[ referrerAddr ].referree_tokenBalance +=
                holderData[ holder ].tokenBalance;

            // Update MinMax according to this referrer's score.
            priv_updateMinMaxScores_referral(
                minMaxRefCpy,
                holderData[ referrerAddr ].referree_etherContributed,
                holderData[ referrerAddr ].referree_timeFactors,
                holderData[ referrerAddr ].referree_tokenBalance
            );

            // Move to the higher-level referrer.
            referrerAddr = holderData[ referrerAddr ].referrer;
        }

        // Update MinMax Referral Scores if needed.
        if( keccak256( abi.encode( minMaxRefCpy ) ) != 
            keccak256( abi.encode( minMaxReferralScores ) ) )
            minMaxReferralScores = minMaxRefCpy;

        return _referrerAddress;
    }


    /**
     *  Sets our random seed to some value.
     *  Should be called from Lottery, after obtaining random seed from
     *  the Randomness Provider.
     */
    function setRandomSeed( uint _seed )
                                                    external
                                                    lotteryOnly
    {
        randomSeed = uint64( _seed );
    }


    /**
     *  Initialization function.
     *  Here, we bind our contract to the Lottery contract that 
     *  this Storage belongs to.
     *  The parent lottery must call this function - hence, we set
     *  "lottery" to msg.sender.
     *
     *  When this function is called, our contract must be not yet
     *  initialized - "lottery" address must be Zero!
     *
     *  Here, we also set our Winner Algorithm config, which is a
     *  subset of LotteryConfig, fitting into 1 storage slot.
     */
    function initialize(
            WinnerAlgorithmConfig memory _wcfg )
                                                        public
    {
        require( address( lottery ) == address( 0 )/*,
                 "Storage is already initialized!" */);

        // Set the Lottery address (msg.sender can't be zero),
        // and thus, set our contract to initialized!
        lottery = msg.sender;

        // Set the Winner-Algo-Config.
        algConfig = _wcfg;

        // NOT-NEEDED: Set initial min-max scores: min is INT_MAX.
        /*minMaxScores.holderScore_etherContributed_min = int80( 2 ** 78 );
        minMaxScores.holderScore_timeFactors_min    = int80( 2 ** 78 );
        minMaxScores.holderScore_tokenBalance_min   = int80( 2 ** 78 );
        */
    }


    // ==================== Views ==================== //


    // Returns the current random seed.
    // If the seed hasn't been set yet (or set to 0), returns 0.
    //
    function getRandomSeed()
                                                    external view
    returns( uint )
    {
        return randomSeed;
    }


    // Check if Winner Selection Algorithm has beed executed.
    //
    function minedSelection_algorithmAlreadyExecuted()
                                                        external view
    returns( bool )
    {
        return algorithmCompleted;
    }

    /**
     *  After lottery has completed, this function returns if "addr"
     *  is one of lottery winners, and the position in winner rankings.
     *  Function is used to obtain the ranking position before
     *  calling claimWinnerPrize() on Lottery.
     *
     *  This function should be called off-chain, and then using the
     *  retrieved data, one can call claimWinnerPrize().
     */
    function minedSelection_getWinnerStatus(
            address addr )
                                                        public view
    returns( bool isWinner, 
             uint32 rankingPosition )
    {
        // Loop through the whole winner indexes array, trying to
        // find if "addr" is one of the winner addresses.
        for( uint16 i = 0; i < numberOfWinners; i++ )
        {
            // Check if holder on this winner ranking's index position
            // is addr, if so, good!
            uint pos = sortedWinnerIndexes[ i / 16 ].indexes[ i % 16 ];

            if( holders[ pos ] == addr )
            {
                return ( true, i );
            }
        }

        // The "addr" is not a winner.
        return ( false, 0 );
    }

    /**
     *  Checks if address is on specified winner ranking position.
     *  Used in Lottery, to check if msg.sender is really the 
     *  winner #rankingPosition, as he claims to be.
     */
    function minedSelection_isAddressOnWinnerPosition( 
            address addr,
            uint32  rankingPosition )
                                                    external view
    returns( bool )
    {
        if( rankingPosition >= numberOfWinners )
            return false;

        // Just check if address at "holders" array 
        // index "sortedWinnerIndexes[ position ]" is really the "addr".
        uint pos = sortedWinnerIndexes[ rankingPosition / 16 ]
                    .indexes[ rankingPosition % 16 ];

        return ( holders[ pos ] == addr );
    }


    /**
     *  Returns an array of all winner addresses, sorted by their
     *  ranking position (winner #1 first, #2 second, etc.).
     */
    function minedSelection_getAllWinners()
                                                    external view
    returns( address[] memory )
    {
        address[] memory winners = new address[] ( numberOfWinners );

        for( uint i = 0; i < numberOfWinners; i++ )
        {
            uint pos = sortedWinnerIndexes[ i / 16 ].indexes[ i % 16 ];
            winners[ i ] = holders[ pos ];
        }

        return winners;
    }


    /**
     *  Compute the Lottery Active Stage Score of a token holder.
     *
     *  This function computes the Active Stage (pre-randomization)
     *  player score, and should generally be used to compute player
     *  intermediate scores - while lottery is still active or on
     *  finishing stage, before random random seed is obtained.
     */
    function getPlayerActiveStageScore( address holderAddr )
                                                            external view
    returns( uint playerScore )
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Check if holderAddr is a holder at all!
        if( holders[ holderIndexes[ holderAddr ] ] != holderAddr )
            return 0;

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );

        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // Now, add bonus score, and compute total player's score:
        // Bonus part, individual score part, and referree score part.
        int totalPlayerScore = 
                holderData[ holderAddr ].bonusScore
                +
                computeHolderIndividualScores(
                    cfg, minMaxCpy, holderData[ holderAddr ] )
                +
                computeReferreeScoresForHolder( 
                    individualToReferralRatio, cfg, 
                    minMaxRefCpy, holderData[ holderAddr ] );


        // Check if total player score <= 0. If so, make it equal
        // to 1, because otherwise randomization won't be possible.
        if( totalPlayerScore <= 0 )
            totalPlayerScore = 1;

        // Now, check if it's not more than max! If so, lowerify.
        // This could have happen'd because of bonus.
        if( totalPlayerScore > maxAvailablePlayerScore )
            totalPlayerScore = maxAvailablePlayerScore;

        // Return the score!
        return uint( totalPlayerScore );
    }



    /**
     *  Internal sub-procedure of the function below, used to obtain
     *  a final, randomized score of a Single Holder.
     */
    function priv_getSingleHolderScore(
            address hold3r,
            int individualToReferralRatio,
            int maxAvailablePlayerScore,
            int SCORE_RAND_FACT,
            WinnerAlgorithmConfig memory cfg,
            MinMaxHolderScores memory minMaxCpy,
            MinMaxReferralScores memory minMaxRefCpy )
                                                        internal view
    returns( uint endScore )
    {
        // Fetch the needed holder data to in-memory hdata variable,
        // to save gas on score part computing functions.
        HolderData memory hdata;

        // Slot 1:
        hdata.etherContributed =
            holderData[ hold3r ].etherContributed;
        hdata.timeFactors =
            holderData[ hold3r ].timeFactors;
        hdata.tokenBalance =
            holderData[ hold3r ].tokenBalance;
        hdata.referreeCount =
            holderData[ hold3r ].referreeCount;

        // Slot 2:
        hdata.referree_etherContributed =
            holderData[ hold3r ].referree_etherContributed;
        hdata.referree_timeFactors =
            holderData[ hold3r ].referree_timeFactors;
        hdata.referree_tokenBalance =
            holderData[ hold3r ].referree_tokenBalance;
        hdata.bonusScore =
            holderData[ hold3r ].bonusScore;


        // Now, add bonus score, and compute total player's score:
        // Bonus part, individual score part, and referree score part.
        int totalPlayerScore = 
                hdata.bonusScore
                +
                computeHolderIndividualScores(
                    cfg, minMaxCpy, hdata )
                +
                computeReferreeScoresForHolder( 
                    individualToReferralRatio, cfg, 
                    minMaxRefCpy, hdata );


        // Check if total player score <= 0. If so, make it equal
        // to 1, because otherwise randomization won't be possible.
        if( totalPlayerScore <= 0 )
            totalPlayerScore = 1;

        // Now, check if it's not more than max! If so, lowerify.
        // This could have happen'd because of bonus.
        if( totalPlayerScore > maxAvailablePlayerScore )
            totalPlayerScore = maxAvailablePlayerScore;


        // Multiply the score by the Random Modulo Adjustment
        // Factor, to get fairer ratio of random-to-determined data.
        totalPlayerScore =  ( totalPlayerScore * SCORE_RAND_FACT ) /
                            ( PRECISION );

        // Score is computed!
        // Now, randomize it, and add to Final Scores Array.
        // We use keccak to generate a random number from random seed,
        // using holder's address as a nonce.

        uint modulizedRandomNumber = uint(
            keccak256( abi.encodePacked( randomSeed, hold3r ) )
        ) % RANDOM_MODULO;

        // Add the random number, to introduce the random factor.
        // Ratio of (current) totalPlayerScore to modulizedRandomNumber
        // is the same as ratio of randRatio_scorePart to 
        // randRatio_randPart.

        return uint( totalPlayerScore ) + modulizedRandomNumber;
    }


    /**
     *  Winner Self-Validation algo-type main function.
     *  Here, we compute scores for all lottery holders iteratively
     *  in O(n) time, and thus get the winner ranking position of
     *  the holder in question.
     *
     *  This function performs essentialy the same steps as the
     *  Mined-variant (executeWinnerSelectionAlgorithm), but doesn't
     *  write anything to blockchain.
     *
     *  @param holderAddr - address of a holder whose rank we want to find.
     */
    function winnerSelfValidation_getWinnerStatus(
            address holderAddr )
                                                        internal view
    returns( bool isWinner, uint rankingPosition )
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Can only be performed if algorithm is WinnerSelfValidation!
        require( cfg.endingAlgoType ==
                 uint8(Lottery.EndingAlgoType.WinnerSelfValidation)/*,
                 "Algorithm cannot be performed on current Algo-Type!" */);

        // Check if holderAddr is a holder at all!
        require( holders[ holderIndexes[ holderAddr ] ] == holderAddr/*,
                 "holderAddr is not a lottery token holder!" */);

        // Now, we gotta find the winners using a Randomized Score-Based
        // Winner Selection Algorithm.
        //
        // During transfers, all player intermediate scores 
        // (etherContributed, timeFactors, and tokenBalances) were
        // already set in every holder's HolderData structure,
        // during operations of updateHolderData_preTransfer() function.
        //
        // Minimum and maximum values are also known, so normalization
        // will be easy.
        // All referral tree score data were also properly propagated
        // during operations of updateAndPropagateScoreChanges() function.
        //
        // All we block.timestamp have to do, is loop through holder array, and
        // compute randomized final scores for every holder.

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );


        // Random Factor of scores, to maintain random-to-determined
        // ratio equal to specific value (1:5 for example - 
        // "randPart" == 5/*, "scorePart" */== 1).
        //
        // maxAvailablePlayerScore * FACT   ---   scorePart
        // RANDOM_MODULO                    ---   randPart
        //
        //                                  RANDOM_MODULO * scorePart
        // maxAvailablePlayerScore * FACT = -------------------------
        //                                          randPart
        //
        //              RANDOM_MODULO * scorePart
        // FACT = --------------------------------------
        //          randPart * maxAvailablePlayerScore

        int SCORE_RAND_FACT =
            ( PRECISION * int(RANDOM_MODULO * cfg.randRatio_scorePart) ) /
            ( int(cfg.randRatio_randPart) * maxAvailablePlayerScore );


        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // How many holders had higher scores than "holderAddr".
        // Used to obtain the final winner rank of "holderAddr".
        uint numOfHoldersHigherThan = 0;

        // The final (randomized) score of "holderAddr".
        uint holderAddrsFinalScore = priv_getSingleHolderScore(
            holderAddr,
            individualToReferralRatio,
            maxAvailablePlayerScore,
            SCORE_RAND_FACT,
            cfg, minMaxCpy, minMaxRefCpy );

        // Index of holderAddr.
        uint holderAddrIndex = holderIndexes[ holderAddr ];


        // Loop through all the allowed holders.
        for( uint i = 0; 
             i < ( holders.length < SELFVALIDATION_MAX_NUMBER_OF_HOLDERS ? 
                   holders.length : SELFVALIDATION_MAX_NUMBER_OF_HOLDERS );
             i++ )
        {
            // Skip the holderAddr's index.
            if( i == holderAddrIndex )
                continue;

            // Compute the score using helper function.
            uint endScore = priv_getSingleHolderScore(
                holders[ i ],
                individualToReferralRatio,
                maxAvailablePlayerScore,
                SCORE_RAND_FACT,
                cfg, minMaxCpy, minMaxRefCpy );

            // Check if score is higher than HolderAddr's, and if so, check.
            if( endScore > holderAddrsFinalScore )
                numOfHoldersHigherThan++;
        }

        // All scores are checked!
        // Now, we can obtain holderAddr's winner rank based on how
        // many scores were above holderAddr's score!

        isWinner = ( numOfHoldersHigherThan < cfg.winnerCount ); 
        rankingPosition = numOfHoldersHigherThan;
    }



    /**
     *  Rolled-Randomness algo-type main function.
     *  Here, we only compute the score of the holder in question,
     *  and compare it to maximum-available final score, divided
     *  by no-of-winners.
     *
     *  @param holderAddr - address of a holder whose rank we want to find.
     */
    function rolledRandomness_getWinnerStatus(
            address holderAddr )
                                                        internal view
    returns( bool isWinner, uint rankingPosition )
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Can only be performed if algorithm is RolledRandomness!
        require( cfg.endingAlgoType ==
                 uint8(Lottery.EndingAlgoType.RolledRandomness)/*,
                 "Algorithm cannot be performed on current Algo-Type!" */);

        // Check if holderAddr is a holder at all!
        require( holders[ holderIndexes[ holderAddr ] ] == holderAddr/*,
                 "holderAddr is not a lottery token holder!" */);

        // Now, we gotta find the winners using a Randomized Score-Based
        // Winner Selection Algorithm.
        //
        // During transfers, all player intermediate scores 
        // (etherContributed, timeFactors, and tokenBalances) were
        // already set in every holder's HolderData structure,
        // during operations of updateHolderData_preTransfer() function.
        //
        // Minimum and maximum values are also known, so normalization
        // will be easy.
        // All referral tree score data were also properly propagated
        // during operations of updateAndPropagateScoreChanges() function.
        //
        // All we block.timestamp have to do, is loop through holder array, and
        // compute randomized final scores for every holder.

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );


        // Random Factor of scores, to maintain random-to-determined
        // ratio equal to specific value (1:5 for example - 
        // "randPart" == 5, "scorePart" == 1).
        //
        // maxAvailablePlayerScore * FACT   ---   scorePart
        // RANDOM_MODULO                    ---   randPart
        //
        //                                  RANDOM_MODULO * scorePart
        // maxAvailablePlayerScore * FACT = -------------------------
        //                                          randPart
        //
        //              RANDOM_MODULO * scorePart
        // FACT = --------------------------------------
        //          randPart * maxAvailablePlayerScore

        int SCORE_RAND_FACT =
            ( PRECISION * int(RANDOM_MODULO * cfg.randRatio_scorePart) ) /
            ( int(cfg.randRatio_randPart) * maxAvailablePlayerScore );


        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // The final (randomized) score of "holderAddr".
        uint holderAddrsFinalScore = priv_getSingleHolderScore(
            holderAddr,
            individualToReferralRatio,
            maxAvailablePlayerScore,
            SCORE_RAND_FACT,
            cfg, minMaxCpy, minMaxRefCpy );

        // Now, compute the Max-Final-Random Score, divide it
        // by the Holder Count, and get the ranking by placing this
        // holder's score in it's corresponding part.
        //
        // In this approach, we assume linear randomness distribution.
        // In practice, distribution might be a bit different, but this
        // approach is the most efficient.
        //
        // Max-Final-Score (randomized) is the highest available score
        // that can be achieved, and is made by adding together the
        // maximum availabe Player Score Part and maximum available
        // Random Part (equals RANDOM_MODULO).
        // These parts have a ratio equal to config-specified
        // randRatio_scorePart to randRatio_randPart.
        //
        // So, if player's active stage's score is low (1), but rand-part
        // in ratio is huge, then the score is mostly random, so 
        // maxFinalScore is close to the RANDOM_MODULO - maximum random
        // value that can be rolled.
        //
        // If, however, we use 1:1 playerScore-to-Random Ratio, then
        // playerScore and RandomScore make up equal parts of end score,
        // so the maxFinalScore is actually two times larger than
        // RANDOM_MODULO, so player needs to score more
        // player-points to get larger prizes.
        //
        // In default configuration, playerScore-to-random ratio is 1:3,
        // so there's a good randomness factor, so even the low-scoring
        // players can reasonably hope to get larger prizes, but
        // the higher is player's active stage score, the more
        // chances of scoring a high final score a player gets, with
        // the higher-end of player scores basically guaranteeing
        // themselves a specific prize amount, if winnerCount is
        // big enough to overlap.

        int maxRandomPart      = int( RANDOM_MODULO - 1 );
        int maxPlayerScorePart = ( SCORE_RAND_FACT * maxAvailablePlayerScore )
                                 / PRECISION;

        uint maxFinalScore = uint( maxRandomPart + maxPlayerScorePart );

        // Compute the amount that single-holder's virtual part
        // might take up in the max-final score.
        uint singleHolderPart = maxFinalScore / holders.length;

        // Now, compute how many single-holder-parts are there in
        // this holder's score.
        uint holderAddrScorePartCount = holderAddrsFinalScore /
                                        singleHolderPart;

        // The ranking is that number, minus holders length.
        // If very high score is scored, default to position 0 (highest).
        rankingPosition = (
            holderAddrScorePartCount < holders.length ?
            holders.length - holderAddrScorePartCount : 0
        );

        isWinner = ( rankingPosition < cfg.winnerCount );
    }


    /**
     *  Genericized, algorithm type-dependent getWinnerStatus function.
     */
    function getWinnerStatus(
            address addr )
                                                        external view
    returns( bool isWinner, uint32 rankingPosition )
    {
        bool _isW;
        uint _rp;

        if( algConfig.endingAlgoType == 
            uint8(Lottery.EndingAlgoType.RolledRandomness) )
        {
            (_isW, _rp) = rolledRandomness_getWinnerStatus( addr );
            return ( _isW, uint32( _rp ) );
        }

        if( algConfig.endingAlgoType ==
            uint8(Lottery.EndingAlgoType.WinnerSelfValidation) )
        {
            (_isW, _rp) = winnerSelfValidation_getWinnerStatus( addr );
            return ( _isW, uint32( _rp ) );
        }

        if( algConfig.endingAlgoType ==
            uint8(Lottery.EndingAlgoType.MinedWinnerSelection) )
        {
            (_isW, _rp) = minedSelection_getWinnerStatus( addr );
            return ( _isW, uint32( _rp ) );
        }
    }

}
/**
 *  The Lottery Storage contract.
 *
 *  This contract is used to store all Holder Data of a specific lottery
 *  contract - that includes lottery token holders list, and every
 *  holder's intermediate scores (HolderData structure).
 *
 *  When the lottery, that this storage belongs to, ends, then
 *  this Storage contract also performs the whole winner selection
 *  algorithm.
 *
 *  Also, one of this contract's purposes is to split code,
 *  to avoid the 24kb code size limit error.
 *
 *  Notice, that Lottery and LotteryStorage contracts must have a
 *  1:1 relationship - every Lottery has only one Storage, and
 *  every Storage belongs to only one Lottery.
 *
 *  The LotteryStorage contracts are being created from the
 *  LotteryStorageFactory contract, and only after that, the parent
 *  Lottery is created, so Lottery must initialize it's Storage,
 *  by calling initialize() function on freshly-created Storage,
 *  which set's the Lottery address, and locks it.
 */
function priv_updateMinMaxScores_individual(
        MinMaxHolderScores memory minMax,
        int40 _etherContributed,
        int40 _timeFactors,
        int40 _tokenBalance )
                                                internal
                                                pure
{
    // etherContributed:
    if( _etherContributed > 
        minMax.holderScore_etherContributed_max )
        minMax.holderScore_etherContributed_max = 
            _etherContributed;

    if( _etherContributed <
        minMax.holderScore_etherContributed_min )
        minMax.holderScore_etherContributed_min = 
            _etherContributed;

    // timeFactors:
    if( _timeFactors > 
        minMax.holderScore_timeFactors_max )
        minMax.holderScore_timeFactors_max = 
            _timeFactors;

    if( _timeFactors <
        minMax.holderScore_timeFactors_min )
        minMax.holderScore_timeFactors_min = 
            _timeFactors;

    // tokenBalance:
    if( _tokenBalance > 
        minMax.holderScore_tokenBalance_max )
        minMax.holderScore_tokenBalance_max = 
            _tokenBalance;

    if( _tokenBalance <
        minMax.holderScore_tokenBalance_min )
        minMax.holderScore_tokenBalance_min = 
            _tokenBalance;
}

/**
 *  Update Min & Max values for individual holder scores.
 */