import random

import numpy as np
import pandas as pd
from cloudmesh.common.console import Console

from cloudmesh.timeseries.city import City3
from cloudmesh.timeseries.date import Date
from cloudmesh.timeseries.magic import Magic
from cloudmesh.timeseries.sample import Sample
from cloudmesh.timeseries.source import Cases
from cloudmesh.timeseries.source import Death
from cloudmesh.timeseries.source import Mobility
from cloudmesh.timeseries.transform import Normalize
from cloudmesh.timeseries.transform import Transform


class Prepare:

    def __init__(self,
                 source="real",
                 start_date="2020-01-22",
                 end_date="2020-04-11"):
        """
        initializes the prepare method. By default real data is used. if source
        is set to "magic" The magic fit is used

        :param source: real or magic
        :param start_date: string
        :param end_date: string
        """
        self.start_date = start_date
        self.end_date = end_date
        self.source = source

        Console.info('mobility data is not avaible after  end_date="2020-04-11"')
        Console.info('death and case data are available till end_date="2020-04-22"')

        self.cases = Cases()
        self.death = Death()
        self.city = City3()
        self.mobility = Mobility()

        if source == 'magic':
            days = 130
            magic_cases = Magic(file="../data/fits-cases.txt", days=days);
            self.cases.data = magic_cases.data
            magic_death = Magic(file="../data/fits-deaths.txt", days=days);
            self.death.data = magic_death.data

            # remove 101
            if 101 in self.cases.data.columns:
                self.cases.data = self.cases.data.drop(columns=[101])
            if 101 in self.death.data.columns:
                self.death.data = self.death.data.drop(columns=[101])



        # read the mobility data and restrict to 110 fips

        fips = self.cases.data.columns
        self.mobility.data = self.mobility.data[fips]

        # select dates between start and end data

        self.cases.data = Date.between(self.cases.data, start_date, end_date)
        self.death.data = Date.between(self.death.data, start_date, end_date)
        self.mobility.data = Date.between(self.mobility.data, start_date, end_date)

    def restrict_cities(self, fips=None, kind=None):
        if fips is not None:
            df = self.city.data[fips].copy()
            self.city.data = df

            df = self.death.data[fips].copy()
            self.death.data = df
            df = self.cases.data[fips].copy()
            self.cases.data = df
            df = self.mobility.data[fips].copy()
            self.mobility.data = df

        elif kind == "death":
            self.restrict_cities(fips=self.death.data.columns)
        elif kind == "cases":
            self.restrict_cities(fips=self.cases.data.columns)

    def normalize_city(self):
        df = self.city.data

        df = df.transpose()
        df['norm_pop'] = np.log(df['Population'].astype(float))
        df = df.transpose()

        zero_one = ['Insurance', 'Nbeds',
       'Nbeds_per1000', 'Nhosp', 'Estbeds', 'senior_percent', 'black_percent',
       'Percentblacks', 'Percenthispanics', 'pop_density_2010',
       'poverty_percent', 'svi_minority', 'svi_overall', 'CASTHMA', 'HIGHCHOL',
       'DIABETES', 'OBESITY', 'CANCER', 'STROKE', 'MHLTH', 'CSMOKING',
       'CHOLSCREEN', 'INSURANCE', 'CHD', 'CHECKUP', 'KIDNEY', 'BINGE', 'LPA',
       'ARTHRITIS', 'BPMED', 'PHLTH', 'BPHIGH', 'COPD', 'norm_pop']

        pop = ['Nbeds', 'Nhosp', 'Estbeds']

        for fips in self.cities:

            population = self.population(fips)
            data = df[fips]

            for field in pop:
                data[field] = (data[field] / population) ** .5

        df = df.transpose()
        df = Normalize.columns(df, zero_one)
        df = Normalize.columns(df, zero_one)

        self.city.data = df.transpose()



    def normalize(self):

        #
        # CONVERT TO DAILY CHANGES
        #
        self.cases.data = Transform.daily_change(self.cases.data)
        self.death.data = Transform.daily_change(self.death.data)

        self.death.data = Normalize.remove_negative_values(self.death.data, value=0)
        self.cases.data = Normalize.remove_negative_values(self.cases.data, value=0)

        #
        # NORMALIZE BY POPULATION  sqrt ( n / pop)
        #

        for fips in self.cities:
            population = self.population(fips)
            self.cases.data[fips] = (self.cases.data[fips] / population) ** .5
            self.death.data[fips] = (self.death.data[fips] / population) ** .5

        #
        # NORMALIZE BETWEEN 0 to 1
        #
        self.death.data = Normalize.zero_to_one(self.death.data)
        self.cases.data = Normalize.zero_to_one(self.cases.data)

        #
        # just map mobility betwene 0 to 1
        #
        self.mobility.data = Normalize.zero_to_one(self.mobility.data)

    def population(self, fips):
        return self.city.data[fips]['Population']

    @property
    def cities(self):
        return list(self.city.data.columns)

    @property
    def attributes(self):
        return list(self.city.data.index)

    def Print(self):
        print()
        for d in [self.cases, self.death, self.mobility]:
            print(d.kind, d.data.shape, d.first_day, "...", d.last_day)
        print("City", self.city.data.shape,
              " ".join(self.city.data.index[:3]),
              "...",
              " ".join(self.city.data.index[-3:]))

    def multivariate_fips(self, fips, variates, fields, include_fips=False):
        df = pd.DataFrame()
        i = 0
        for i in range(0, len(variates)):
            d = variates[i]
            df[d.kind] = d.data[fips]
        for f in fields:
            value = self.city.data[fips][f]
            df.insert(0, f, value)
        if include_fips:
            df.insert(0, 'fips', fips)
        self.df = df
        return df

    def samples_fips(self, fips, in_data, out_data, n_steps_in=3, n_steps_out=1):

        fields = list(in_data)
        if 'Death' in in_data:
            fields.remove('Death')
        if 'Cases' in in_data:
            fields.remove('Cases')
        if 'Mobility' in in_data:
            fields.remove('Mobility')

        df = self.multivariate_fips(fips,
                                    [self.cases, self.death, self.mobility],
                                    fields, include_fips=False)

        self.df = df

        df_in = df[in_data]
        df_out = df[out_data]

        array_in = df_in.to_numpy()
        array_out = df_out.to_numpy()

        X, y = list(), list()

        for i in range(len(array_in)):
            # find the end of this pattern
            end_ix = i + n_steps_in
            out_end_ix = end_ix + n_steps_out
            # check if we are beyond the sequence
            if out_end_ix > len(array_in):
                break
            # gather input and output parts of the pattern
            seq_x, seq_y = array_in[i:end_ix], array_out[end_ix:out_end_ix]
            X.append(seq_x)
            y.append(seq_y[0])

        return np.array(X), np.array(y)

    def samples(self, in_data, out_data, fipses=None, n_steps_in=3, n_steps_out=1):
        X = []
        y = []
        if fipses is None:
            fipses = self.city.data.columns
        for fips in fipses:
            X_fips, y_fips = self.samples_fips(fips, in_data, out_data, n_steps_in=n_steps_in, n_steps_out=n_steps_out)
            X, y = Sample.sample_merge(X, y, X_fips, y_fips)
        self.X = np.array(X)
        self.y = np.array(y)
        return self.X, self.y

    def get_samples(self):
        return self.X, self.y

    def shuffle(self):
        order = list(range(0, len(self.X)))
        random.shuffle(order)
        X = list()
        y = list()
        for i in order:
            X.append(self.X[i])
            y.append(self.y[i])
        self.X = np.array(X)
        self.y = np.array(y)
        return self.X, self.y

    """
    def old_samples_fips(self, fips, in_variates, fields, n_steps_in=3, n_steps_out=1):
        df = self.multivariate_fips(fips, in_variates, fields, include_fips=False)

        X, y = list(), list()

        in_data = df.copy()

        sequence = []
        for row in range(0, len(in_data)):
            v = in_data.iloc[[row]].values.tolist()[0]
            sequence.append(v)

        for i in range(len(sequence)):
            # find the end of this pattern
            end_ix = i + n_steps_in
            out_end_ix = end_ix + n_steps_out
            # check if we are beyond the sequence
            if out_end_ix > len(sequence):
                break
            # gather input and output parts of the pattern
            seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix]
            X.append(seq_x)
            y.append(seq_y)

        # drop fields from y
        for i in range(0, len(y)):
            y[i] = y[i][0][len(fields):]

        # return np.array(X), np.array(y)
        return X, y

    def old_sample(self, variates, fields, fipses=None, n_steps_in=3, n_steps_out=1):
        X = []
        y = []
        if fipses is None:
            fipses = self.city.data.columns
        for fips in fipses:
            X_fips, y_fips = self.samples_fips(fips, variates, fields, n_steps_in=n_steps_in, n_steps_out=n_steps_out)
            X, y = Sample.sample_merge(X, y, X_fips, y_fips)
        self.X = X
        self.y = y
        return X, y
    """
