import numpy as np
import pandas as pd
from tqdm import tqdm
pd.set_option('mode.chained_assignment', None)


from sortasurvey import observing


class Survey:
    """
    Loads in survey information and the vetted survey sample.

    Parameters
    ----------
    args : argparse.Namespace
        command line arguments

    Attributes
    ----------
    verbose : bool
        verbose output
    iter : int
        number of selection process iterations. Default is `1` (via args.iter).
    step : int
        iteration number
    programs : pandas.DataFrame
        pandas dataframe containing survey information
    sciences : pandas.DataFrame
        copy of the 'programs' pandas dataframe
    sample : pandas.DataFrame
        pandas dataframe containing the sample to select targets from
    candidates : pandas.DataFrame
        copy of the 'sample' pandas dataframe
    track : dict
        logs each iteration of the target selection

    """
    
    def __init__(self, args, path_sample='info/TOIs_perfect.csv', path_survey='info/survey_info.csv',
                 path_priority='info/high_priority.csv', path_ignore='info/no_no.csv', iter=1,
                 hours_per_night=10., pool=50., progress=True):

        self.verbose = args.verbose
        self.save = args.save
        self.outdir = args.outdir
        self.iter = args.iter
        self.progress = args.progress
        self.track = {}
        for n in np.arange(1,self.iter+1):
            self.track[n] = {}
        if self.verbose:
            print('\n -- prioritization starting --\n\n   - loading sample and survey science information')
        self.get_sample(args)
        self.get_programs(args)
        self.get_seeds()
        if self.iter > 1:
            self.emcee = True
            if self.verbose and self.progress:
                self.pbar = tqdm(total=self.iter)
        else:
            self.emcee = False


    def get_sample(self, args, dec=-30., ruwe=2.):
        """
        Fixes the sample based on specific survey needs.
        For TKS as a whole, this only required that the target
        is observable (dec > -30.) and possessed a reasonable
        Gaia RUWE metric (ruwe < 2.).

        """
        df = pd.read_csv(args.path_survey, comment="#")
        self.programs = df.programs.values.tolist()
        sample = pd.read_csv(args.path_sample)
        self.path_sample = args.path_sample
        self.sample = sample.query("dec > %f and ruwe < %f"%(dec, ruwe))
        self.remove_bad()
        self.add_columns()
        # science-case-specific metrics
        self.get_sc3_info()
        self.get_counts()
        self.candidates = self.sample.copy()
        

    def remove_bad(self, disp=['FP','EB','NEB','BEB','SV','BD','NPC','SB1','SB2','FA']):
        """
        Removes unfavorable targets from the survey. In this case, this includes false 
        alarms and/or false positives, including nearby/blended eclipsing binaries as well
        as spectroscopic false positives (e.g., SB1, SB2).

        """
        for bad in disp:
            self.sample.query("disp != '%s'"%bad, inplace=True)
        self.sample.query("drop == False", inplace=True)
        self.sample.query("finish == False", inplace=True)
    

    def add_columns(self, cols=["npl","select_DG","in_other_programs","n_select"]):
        """
        Adds in additional columns that might be relevant for the target selection.

        """

        cols += ["in_%s"%program for program in self.programs]
        for col in cols:
            if col == 'npl':
                self.sample[col] = self.sample.groupby('tic')['tic'].transform('count')
            else:
                self.sample[col] = [0]*len(self.sample)


    def get_counts(self):
        """
        Compute the number of targets that passed the different vetting steps.

        """
        query = self.sample.drop_duplicates(subset='tic')
        new_query = query.query("photo_vetting == 'passed' and spec_vetting != 'failed' and spec_vetting != 'do not observe' and ao_vetting != 'failed'")
        self.passed_tks = len(query)
        self.passed_vet = len(new_query)
        if self.verbose:
            print('   - %d targets make the standard survey cuts'%self.passed_tks)
            print('   - %d have also passed various vetting steps'%self.passed_vet)
            print('   - ranking algorithm initialized')


    def get_programs(self, args):
        """
        Get the survey programs from the provided csv (via args.path_survey)

        """

        programs = pd.read_csv(args.path_survey, comment="#")
        programs.set_index('programs', inplace=True, drop=False)
        nono = pd.read_csv(args.path_ignore)
        priority = pd.read_csv(args.path_priority)

        for program in programs.index.values.tolist():
            # get initial allocation
            programs.loc[program,'total_time'] = (programs.loc[program,'allocations']/(np.sum(programs.allocations)))*args.nights*args.hours
            if not np.isnan(programs.loc[program,'remaining_hours']):
                programs.loc[program,'total_time'] += programs.loc[program,'remaining_hours']
            programs.loc[program,'remaining_hours'] = programs.loc[program,'total_time']
            # adjust filter for priority/ignore targets
            high_priority = [float(target) for target in priority[program].values if target != '-']
            no_no = [float(target) for target in nono[program].values if target != '-']
            for toi in (high_priority + no_no):
                programs.loc[program,'filter'] += " and toi != %.2f"%toi
            if programs.loc[program,'n_maximum'] != -1:
                programs.loc[program,'n_targets_left'] = programs.loc[program,'n_maximum']
            else:
                query = self.sample.query(programs.loc[program,'filter'])
                targets = query.toi.values.tolist() + high_priority
                targets = [int(np.floor(each)) for each in targets]
                programs.loc[program,'n_targets_left'] = len(list(set(targets)))
            # get the prioritization metric
            prioritize_by=[]
            for each in programs.loc[program,'prioritize_by'].strip().split('|'):
                prioritize_by.append(each)
            programs.at[program,'prioritize_by'] = prioritize_by
            ascending_by=[]
            for each in programs.loc[program,'ascending_by'].strip().split('|'):
                if each == 'True' or str(each) == 'True' or str(each) == 'TRUE':
                    ascending_by.append(True)
                else:
                    ascending_by.append(False)
            programs.at[program,'ascending_by'] = ascending_by
        programs['pick_number'] = np.zeros(len(programs)).astype('int64')
        programs['n_targets_left'] = programs['n_targets_left'].astype('int64')
        programs.rename(columns={'programs':'name'}, inplace=True)
        programs.drop(columns=['allocations'], inplace=True)
        programs = programs.to_dict('index')
        for program in programs:
            programs[program]['high_priority'] = [float(target) for target in priority[program].values if target != '-']
        programs = pd.DataFrame.from_dict(programs, orient='index', columns=['name','method','filter','prioritize_by','ascending_by','remaining_hours','n_maximum','total_time','high_priority','n_targets_left','pick_number'])
        programs.to_csv('%s_copy.%s'%(args.path_survey.split('.')[0],args.path_survey.split('.')[-1]))
        self.programs = programs.copy()
        self.sciences = self.programs.copy()
        if args.special is not None:
            self.get_special(args)
        else:
            self.special = {}


    def get_special(self, args, special=[]):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        for program in args.special:
            if program.startswith('o') or program.startswith('O'):
                special.append('T%s'%program.upper())
            else:
                special.append('SC%s'%program.upper())
        sp={}
        for program in special:
            sp[program]={}
        self.special = sp.copy() 


    # science-case-specific functions
    def get_sc3_info(self, include_qlp=True, mask=None):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        if not include_qlp:
            mask = self.sample['source'] == 'spoc'
        self.calculate_TSM(mask=mask)
        sc3_df = self.sample.copy()
        sc3_mask = pd.notnull(sc3_df['TSM'])
        rp_bins = 10**(np.linspace(0,1,6))
        rp_bins[-1] = 11.2
        sinc_bins = 10**(np.linspace(-1,4,6))
        teff_bins = np.array([2500,3900,5200,6500])
        bins = [rp_bins, sinc_bins, teff_bins]
        sc3_df['rt_5sig'] = sc3_df['rt_5sig'].replace(0., 1e-2)
        sc3_mask &= pd.notnull(sc3_df['rt_5sig'])
        sc3_df = sc3_df[sc3_mask]
        sc3_df['X'] = sc3_df['TSM']/sc3_df['rt_5sig']
        sc3_df = self.sc3_binning_function(sc3_df, bins, sort_val='X')
        sc3_df['SC3_bin_rank'] = sc3_df['SC3_bin_rank'].replace(0., np.nan)
        cols_to_use = ['toi','SC3_bin_rank']
        self.sample = self.sample.merge(sc3_df[cols_to_use], how='left', on='toi')


    def calculate_TSM(self, mask=None):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        self.sample['TSM'] = [np.nan]*len(self.sample)
        if mask is None:
            mask = np.ones(len(self.sample), dtype=bool)
        for key in ['rp','mp','a_to_R','teff','jmag','r_s']:
            mask &= pd.notnull(self.sample[key])
        def rp_to_scale_factor(rp):
            if rp < 1.5:
                scale_factor = 0.19
            elif rp > 1.5 and rp < 2.75:
                scale_factor = 1.26
            elif rp > 2.75 and rp < 4:
                scale_factor = 1.28
            else:
                scale_factor = 1.15
            return scale_factor
        scale_factors = self.sample.loc[mask,'rp'].apply(rp_to_scale_factor)
        Teqs = self.sample.loc[mask,'teff']*np.sqrt(np.reciprocal(self.sample.loc[mask,'a_to_R'])*np.sqrt(0.25))
        numerator = scale_factors*np.power(self.sample.loc[mask, 'rp'], 3)*Teqs*np.power(10, -1*self.sample.loc[mask,'jmag']/5)
        denominator = self.sample.loc[mask,'mp']*np.square(self.sample.loc[mask,'r_s'])
        self.sample.loc[mask,'TSM'] = numerator/denominator


    def sc3_binning_function(self, df, bins, sort_val='TSM', num_to_rank=5):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        pre_bin = df.assign(
            rp_bin = pd.cut(df['rp'],bins=bins[0],labels = [1,2,3,4,5]),
            sinc_bin = pd.cut(df['sinc'],bins=bins[1],labels = [1,2,3,4,5]),
            teff_bin = pd.cut(df['teff'],bins=bins[2],labels = [1,2,3])
        )
        binned_df = pre_bin.dropna(subset=['rp_bin','sinc_bin','teff_bin']).\
                groupby(['rp_bin','sinc_bin','teff_bin']).apply(lambda _pre_bin:\
                _pre_bin.sort_values(by=[sort_val],ascending=False))\
                .reset_index(level = 3,drop=True)
        all_idx = binned_df.index.tolist()
        unique_idx = []
        for element in all_idx:
            if element not in unique_idx:
                unique_idx.append(element)
        binned_df['SC3_bin_rank'] = np.zeros(len(binned_df))
        for idx in unique_idx:
            bin_items = len(binned_df.loc[idx].sort_values(sort_val,ascending=False).iloc[0:num_to_rank]['toi'])
            for i in range(1, num_to_rank+1):
                if bin_items == i and bin_items <= num_to_rank:
                    for j in range(i):
                        binned_df.loc[binned_df['toi'] == binned_df.loc[idx].sort_values\
                                (sort_val,ascending=False).iloc[0:num_to_rank]['toi'].iloc[j],'SC3_bin_rank'] = j+1
                elif bin_items > num_to_rank:
                    for j in range(num_to_rank):
                        binned_df.loc[binned_df['toi'] == binned_df.loc[idx].sort_values\
                                (sort_val,ascending=False).iloc[0:num_to_rank]['toi'].iloc[j],'SC3_bin_rank'] = j+1
        return binned_df


    def init_2D_filters(self, program):
        """
        Sets up filters for a more complicated selection criteria.
        This is relevant for SC1B, exploring the possible connection 
        between stellar flux and gaseous envelopes. To study this
        effect, the goal is to randomly but uniformly sample in
        place radius vs. incident flux space. This is done by setting
        up equal bin sizes in linear(rp)-log(sinc) parameter space 
        for a total of nbins=12.

        """
        count=0
        choices={}
        if program == 'SC1B':
            y_bins = np.arange(1,5)
            x_bins = np.logspace(0,2,5)
            science_filter = "rp > %f and rp < %f and period > %f and period < %f"
            n_bins = (len(y_bins)-1)*(len(x_bins)-1)
            for i in range(n_bins):
                x = i%(len(x_bins)-1)
                y = i%(len(y_bins)-1)
                filter = science_filter%(y_bins[y],y_bins[y+1],x_bins[x],x_bins[x+1])
                query = self.candidates.query(filter)
                query = query.drop_duplicates(subset='tic')
                if not query.empty:
                    choices[count] = {}
                    choices[count]['targets_left'] = len(query)
                    choices[count]['filter'] = filter
                    count += 1
            choices = pd.DataFrame.from_dict(choices, orient='index')
            if count != n_bins:
                choose = np.arange(1,count+1,1)
            else:
                choose = np.arange(1,n_bins+1,1)
            # it will randomize the bin filters for one entire round (using all 12 bins)
            choose = np.random.permutation(choose)[:]
            choices = choices.set_index(choose)
            choices = choices.sort_index()
        return choices


    def get_2D_filter(self, program):
        """
        This procedure is called for science cases that wish to uniformly 
        and randomly sample over a 2-dimensional space. 

        """
        pick_number = self.special[program]['pick_number']
        choices = self.special[program]['choices']
        while True:
            n_bins = len(choices)
            # first check if the bin has any targets left
            if pick_number%n_bins == 0:
                # special case for the final bin in the round of choices
                check = choices.loc[n_bins,'targets_left']
                if check != 0:
                    # if it does, use the filter
                    filter = choices.loc[n_bins,'filter']
                    # reset filter choices
                    self.reset_2D_filters(program)
                    break
                else:
                    # if not, reset filter choices
                    self.reset_2D_filters(program)
            else:
                check = choices.loc[pick_number%n_bins,'targets_left']
                if check != 0:
                    # select filter
                    filter = choices.loc[pick_number%n_bins,'filter']
                    break
            # keep going until an appropriate bin/filter is selected
            pick_number += 1
        pick_number += 1
        self.special[program]['pick_number'] = pick_number
        self.sciences.loc[program,'filter'] = filter


    def reset_2D_filters(self, program):
        """
        When all bins have been used, they need to be reset (which includes)
        randomizing the bin order selection.

        """
        choices = self.special[program]['choices']
        new_df = choices.query('targets_left != 0')
        n_bins = len(new_df)
        choose = np.arange(1,n_bins+1,1)
        choose = np.random.permutation(choose)[:]
        choices = new_df.set_index(choose)
        self.special[program]['choices'] = choices.copy()


    def check_2D_overlap(self, pick, program):
        """
        Multi-planet systems (i.e. the same observed target) can fall into more than
        one bin so this needs to be checked at each step, as ewll as the number of 
        available targets per bin.

        """
        choices = self.special[program]['choices']
        tic = pick.tic
        for i in choices.index.values.tolist():
            query = self.candidates.query(choices.loc[i,'filter'])
            if tic in query.tic.values.tolist():
                choices.loc[i, 'targets_left'] -= 1
        self.sciences.loc[program, 'n_targets_left'] = np.sum(choices.targets_left.values.tolist())+1
        self.special[program]['choices'] = choices.copy()
        self.special[program]['stuck'] = 0


    def update_targets(self):
        start = np.array([0]*len(self.candidates))
        for science in self.sciences.index.values.tolist():
            start += self.candidates['in_%s'%science].values.tolist()
        self.candidates['in_other_programs'] = start
 

    def reset_track(self):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        self.reset_programs()
        self.track[self.n][0] = {}
        for program, hours in zip(self.sciences.index.values.tolist(), self.sciences.remaining_hours.values.tolist()):
            self.track[self.n][0][program] = round(hours,3)
        self.track[self.n][0]['total_time'] = round(np.sum(self.sciences.remaining_hours.values.tolist()),3)
        self.priority = 1
        self.i = 1
        np.random.seed(self.seeds[self.n-1])
        if self.verbose and self.progress and self.iter > 1 and self.n != 1:
            self.pbar.update(1)


    def reset_programs(self):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        # make copies of the original dataframes, thus resetting the information
        self.candidates = self.sample.copy()
        self.sciences = self.programs.copy()
        if self.special != {}:
            for program in self.special:
                self.special[program]['stuck'] = 0
                self.special[program]['pick_number'] = 1
                choices = self.init_2D_filters(program)
                self.special[program]['choices'] = choices


    def add_program_pick(self, pick, program):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        self.track[self.n][self.i] = {}
        self.track[self.n][self.i]['program'] = program
        self.track[self.n][self.i]['program_pick'] = self.sciences.loc[program,'pick_number']+1
        self.track[self.n][self.i]['toi'] = float(pick.toi)
        self.track[self.n][self.i]['tic'] = int(pick.tic)


    def update_program_hours(self):
        """
        Get any special science programs that require a more complicated selection process.
        For TKS, SC1B uses a 2D selection process and needs to update the filter at each selection
        step.

        """
        for program, hours in zip(self.sciences.index.values.tolist(), self.sciences.remaining_hours.values.tolist()):
            self.track[self.n][self.i][program] = round(hours,3)
        self.track[self.n][self.i]['total_time'] = round(np.sum(self.sciences.remaining_hours.values.tolist()),3)


    def update(self, pick, program):
        """
        Update appropriate information and tables.

        """
        if program in self.special:
            self.check_2D_overlap(pick, program)
        self.add_program_pick(pick, program)
        self.sciences.loc[program,'n_targets_left'] -= 1
        self.sciences.loc[program,'pick_number'] += 1
        self = observing.check_observing(self, pick, program)
        if not int(pick.in_other_programs):
            net = {program:-1.*(float(pick.actual_cost)/3600.)}
            self.track[self.n][self.i]['overall_priority'] = self.priority
            self.candidates.loc[self.candidates['tic'] == int(pick.tic),'priority'] = int(self.priority)
            self.priority += 1
        else:
            net = observing.adjust_costs(self, pick, program)
            idx = self.candidates.loc[self.candidates['tic'] == int(pick.tic)].index.values.tolist()[0]
            self.track[self.n][self.i]['overall_priority'] = int(self.candidates.loc[idx,'priority'])
        for key in net.keys():
            self.sciences.loc[key,'remaining_hours'] += net[key]
        self.update_program_hours()
        self.candidates.loc[self.candidates['tic'] == int(pick.tic),'in_%s'%program] = 1
        self.update_targets()
        self.i += 1


    def get_seeds(self):
        """
        Ensures reproducibility due to the instrinsic randomness of the algorithm.

        """
        self.seeds = [2222, 5531, 5348, 9632, 3755, 3401, 1061, 9307, 2033, 2114, 3103, 8120, 5442, 9179, 3165, 6114, 8757, 8574, 8078, 7724, 9056, 9066, 8423, 5278, 663, 4542, 6448, 7261, 6999, 7212, 3832, 3199, 6444, 1704, 8872, 2743, 9163, 1293, 8458, 5782, 7144, 9339, 3961, 9127, 4105, 3209, 7662, 5592, 4672, 2365, 8214, 3725, 2088, 1234, 6984, 2756, 3962, 7279, 9686, 112, 8936, 8807, 4149, 2535, 1541, 1422, 7991, 6445, 4384, 570, 9719, 5834, 5372, 1376, 1192, 1499, 8653, 730, 5469, 7541, 6546, 4002, 5677, 9251, 5459, 630, 908, 9074, 2675, 9517, 1015, 5272, 6846, 6820, 4516, 5632, 5671, 2126, 4440, 9670, 7768, 1405, 5330, 1854, 3156, 6949, 1119, 5257, 2999, 4251, 9674, 5362, 5009, 7526, 8293, 4518, 8641, 1365, 2492, 5061, 4804, 2710, 8823, 6637, 9382, 7928, 9219, 7840, 895, 5647, 3966, 6452, 9027, 8673, 1006, 469, 5056, 42, 8067, 7571, 3304, 6795, 9131, 6327, 5781, 5336, 4484, 5137, 3231, 4465, 91, 5135, 3303, 1890, 7593, 359, 6051, 1236, 9967, 3149, 9913, 3114, 9267, 3049, 6089, 6439, 828, 8893, 7708, 6766, 2818, 8745, 8791, 3639, 461, 3917, 8917, 2863, 1865, 9410, 1851, 617, 7563, 915, 1773, 4997, 6121, 8540, 6358, 1630, 5468, 8585, 4959, 8115, 6337, 355, 1977, 4800, 6831, 932, 1028, 8232, 1381, 3260, 2937, 7031, 6310, 5348, 2172, 3321, 4422, 1195, 2021, 481, 731, 5566, 9719, 7468, 9499, 1326, 4071, 7660, 6583, 5067, 5693, 2933, 8679, 9988, 550, 2599, 5536, 3081, 4429, 3592, 8140, 1398, 1481, 6823, 9006, 9264, 6037, 95, 9807, 2768, 4792, 7417, 6095, 8049, 79, 5070, 1457, 3099, 736, 2332, 2228, 146, 3862, 2153, 7800, 8664, 625, 2393, 88, 780, 4266, 9412, 4973, 426, 7742, 4593, 408, 7296, 1981, 867, 7636, 2455, 3519, 3093, 882, 7396, 815, 7717, 4792, 3103, 2747, 290, 8302, 2124, 2516, 3170, 8224, 3693, 5721, 3599, 9778, 5903, 8544, 69, 7648, 4860, 212, 517, 3765, 1401, 8722, 1689, 3281, 3061, 9293, 4954, 4584, 3357, 6380, 5266, 8972, 5578, 9289, 859, 486, 3746, 7928, 7240, 2861, 7615, 651, 5633, 4687, 7439, 2572, 1999, 1476, 5806, 1966, 9249, 3439, 4559, 6899, 5633, 1973, 6469, 1636, 4922, 5059, 7772, 3907, 7410, 1822, 9659, 8230, 3643, 9106, 9524, 8971, 2887, 705, 4252, 6198, 1420, 9063, 5272, 9641, 195, 5217, 1819, 2286, 431, 5379, 26, 7690, 7241, 3735, 2987, 1490, 2807, 5059, 6556, 5921, 3949, 6128, 606, 7636, 1451, 4598, 2446, 9877, 635, 876, 9594, 1742, 5887, 5355, 365, 8197, 7919, 6969, 9736, 1703, 8703, 3358, 8321, 6817, 3617, 9069, 6406, 3938, 3077, 6166, 1546, 4393, 1026, 9479, 2568, 1787, 1434, 8390, 3844, 4028, 5643, 9291, 5072, 8022, 7260, 1209, 5579, 6860, 2871, 2662, 4769, 7361, 7427, 8737, 1608, 6613, 7941, 5619, 6949, 3217, 4204, 1439, 3439, 4521, 4761, 4089, 2066, 9623, 3076, 9230, 1503, 9896, 7110, 2152, 1291, 1339, 5088, 2959, 8092, 5381, 7283, 8831, 8448, 6775, 5414, 5871, 2728, 8828, 6320, 3294, 7953, 4157, 5654, 6890, 5134, 45, 6881, 4237, 9561, 913, 9990, 9667, 650, 1353, 2963, 3896, 4368, 8162, 5630, 5889, 9093, 5298, 17, 7958, 6417, 7574, 6461, 7446, 8398, 5486, 7742, 7503, 1740, 6987, 2238, 1159, 6552, 7968, 440, 1671, 7755, 9214, 1099, 7801, 4910, 878, 3278, 667, 1813, 7540, 2082, 3182, 5580, 3256, 9619, 5890, 8902, 9635, 2516, 864, 823, 9222, 6156, 5011, 7191, 4584, 4112, 9991, 110, 2361, 2709, 6469, 9592, 9668, 6788, 7505, 4174, 3119, 5693, 429, 6224, 3174, 6134, 6902, 9692, 2620, 1532, 7973, 5644, 6105, 2495, 1368, 9342, 3747, 9358, 1039, 311, 5382, 7309, 2482, 1889, 1162, 5620, 8439, 5487, 975, 4845, 4641, 7027, 747, 1016, 5728, 6175, 5252, 598, 4920, 5544, 6273, 9336, 8096, 8059, 2467, 1098, 72, 372, 737, 4500, 2736, 7458, 3742, 3156, 8420, 5311, 8532, 7186, 9113, 7041, 6658, 2370, 2733, 8258, 139, 6127, 4489, 692, 5627, 8139, 9744, 9773, 3674, 9103, 9896, 897, 2939, 8342, 3031, 4991, 3110, 3845, 2214, 5184, 7482, 3367, 5030, 9570, 7613, 1394, 1491, 2570, 5573, 9688, 2731, 8333, 6764, 4922, 4886, 8623, 2301, 8688, 9286, 832, 439, 2502, 8934, 5356, 6584, 6322, 6958, 1542, 9526, 9040, 10000, 8659, 1672, 610, 4050, 6616, 7105, 6073, 9004, 5102, 7781, 1615, 8225, 2511, 3862, 6110, 9382, 5402, 1501, 1972, 6596, 2496, 2523, 2710, 3515, 4024, 7273, 6509, 1913, 8888, 5892, 6173, 1836, 7008, 1328, 6628, 6840, 126, 3190, 4511, 5644, 8944, 6386, 8863, 5022, 5361, 3799, 6701, 750, 785, 2069, 6609, 6429, 7252, 5477, 2309, 9163, 7957, 4056, 8866, 6815, 8583, 2891, 6979, 1242, 3795, 4564, 1785, 1292, 3009, 8132, 3837, 5357, 5549, 9030, 9177, 7603, 3764, 347, 3695, 3836, 7269, 1196, 5401, 4362, 4053, 9416, 2994, 6420, 8527, 7178, 1084, 1582, 967, 7636, 3565, 6510, 4259, 6769, 7106, 1102, 2072, 5721, 4149, 1459, 4861, 39, 1404, 44, 7296, 3745, 2023, 3162, 4885, 9147, 2716, 4395, 9489, 9240, 9882, 3761, 2755, 1862, 9856, 404, 7118, 8258, 5581, 1477, 4694, 463, 598, 9566, 9119, 6289, 5209, 6703, 4719, 8622, 9687, 8361, 5639, 812, 6559, 9332, 6663, 5722, 3930, 8141, 6207, 7787, 1572, 6012, 6052, 609, 6106, 606, 3013, 3915, 6504, 7301, 5596, 1644, 4915, 5623, 943, 1779, 1028, 5734, 8674, 6440, 5126, 5988, 4179, 2955, 9198, 4068, 7912, 6211, 8559, 7260, 193, 7662, 8317, 7231, 181, 1482, 9115, 9971, 4519, 4073, 4300, 2938, 5456, 2939, 3906, 6385, 5011, 9510, 1227, 8649, 4715, 6021, 5418, 3568, 3571, 622, 5414, 1137, 5375, 6263, 4930, 2352, 8565, 6277, 563, 1113, 4847, 1355, 3215, 5297, 8883, 8371, 6917, 5952, 3448, 6154, 52, 2204, 763, 919, 7355, 2095, 8809, 2362, 7590, 5591, 4950, 2817, 7882, 9214, 7549, 5118, 1046, 2298, 7458, 8277, 339, 2443, 8941, 2072, 324, 1350, 5502, 2501, 2680, 5925, 9935, 6294, 5578, 6686, 5888, 5921, 2690, 8177, 2405, 5438, 6754, 2331, 5550, 1591, 9183, 3714, 1097, 7171, 1552, 117, 1135, 1067, 4952, 6742, 3960, 1489, 4201, 2390, 7777, 979, 2114, 9652, 7569, 5795, 4386, 7838, 6684, 1262, 7700, 8091, 1979, 3566, 6058, 5834, 5443, 50, 1658, 6047, 4273, 9477, 3761, 5953, 2732, 7142, 81, 4457, 4703, 5029, 9303, 1936, 468, 5888, 7253, 6611, 1883, 8285, 7267, 2028, 2842, 9302, 4262, 812, 2696, 9879, 2992, 4865, 5031, 6292, 2439, 2261, 8345, 9926, 8960, 2960, 4199]