import os
import pandas as pd

from cloudmesh.common.console import Console

from cloudmesh.common.util import download as file_download
from cloudmesh.timeseries.date import Date as TsDate
import sys


class Data:

    def __init__(self):
        self.data = None
        self.name = None

    def load(self, name=None):
        if name is not None:
            self.name = name
        self.data = Data110.read_data(self.name)

    def drop(self, columns):
        self.data.drop(columns, axis=1, inplace=True)
        pass

    def period(self):
        """
        returns the first and last date of the data

        :return:
        """
        return self.data.index[0], self.data.index[-1]

    def look(self, fips, n):
        """
        prints the first and last n fips

        :param fips:
        :param n:
        :return:
        """
        _data = self.get(fips)
        return f"{fips} " + \
               " ".join([str(x) for x in _data[0:n]]) + \
               " ... " + \
               " ".join([str(x) for x in _data[-n:]])

    def head(self, fips, n):
        """
        prints the first n fips

        :param fips:
        :param n:
        :return:
        """
        _data = self.get(fips)
        return f"{fips} " + \
               " ".join([str(x) for x in _data[0:n]]) \
               + f"... (first {n})"

    def tail(self, fips, n):
        """
        prints the last n fips

        :param fips:
        :param n:
        :return:
        """
        _data = self.get(fips)
        return f"{fips} ... " + \
               " ".join([str(x) for x in _data[-n:]]) \
               + f" (last {n})"

    def range(self, x1, x2):
        return self.data[x1:x2]

    # note this is now a property ad you need to drop the ()
    @property
    def all(self):
        """
        returns the data

        :return:
        """
        return self.data

    def fips(self, fips):
        """
        returns the data for a fips

        :param fips:
        :return:
        """
        return self.get(fips)

    def get(self, fips):
        """
        returns a python array for a fips

        :param fips:
        :return:
        """
        if type(fips) == list:
            print("list not supported")
            return None
        else:
            return self.data[fips].to_list()

    def attributes(self, fips, names, kind='list'):
        """
        returns all attributes for City fips in the specified format

        :param fips:
        :param names:
        :param kind: list, df, series
        :return:
        """
        if type(names) == list:
            data = self.data[fips][names]
            if kind == 'list':
                data = data.to_list()
            elif kind == 'df':
                data = data.to_frame()
            elif kind == 'series':
                pass
        else:
            data = self.attributes(fips, [names], kind="series")[names]
        return data

    def __str__(self):
        """
        returns the string of teh data

        :return:
        """
        return self.data

    def np(self, fips):
        """
        returns the numpy series of the fips

        :param fips:
        :return:
        """
        return np.array(self.get(fips))

    def describe(self, fips=None):
        """
        describes teh data associated with the fips

        :param fips:
        :return:
        """
        if fips is None:
            df = pd.DataFrame(self.data)
            return df.describe()
        else:
            df = pd.DataFrame(self.data[fips])
            return df.describe()

    """
    @staticmethod
    def minmaxnormalize(v):
        max_value = max(v)
        min_value = min(v)
        return [(x - min_value) / (max_value - min_value) for x in v]

    def normalize(self, fips=None, f=None):
        if f is None:
            f = self.minmaxnormalize
        return f(self.get(fips))
    """


# noinspection PyPep8Naming,PyPep8Naming
class Sample(Data):

    # sequence_length, X, y = cases.attribute_samples_from_data(data, city,
    # fips, fields, fixed_input_length, 1,  np=True)

    def dayone(self, count=0):
        """
        finds the first day when the count is found in all

        :param count:
        :return:
        """
        df = self.data.stack()
        df = df[df >= count].dropna()
        first_activity = df.index[0]
        when = first_activity[0]
        day = str(when).replace(" 00:00:00", "")
        fips = first_activity[1]
        return day

    @property
    def first_day(self):
        return str(self.data.index[0]).split(" ")[0]

    @property
    def last_day(self):
        return str(self.data.index[-1]).split(" ")[0]

    @staticmethod
    def attribute_samples_from_data(data, city, fips, fields, n_in, n_out, y_vector=True, np=False):

        X, y = Sample.samples_from_data(data, n_in, n_out, y_vector=y_vector)

        if fields is not None:
            constants = city.attributes(fips, fields)
            V = Sample.sample_insert(X, constants)
        else:
            V = X
        sequence_length = len(V[0])
        if np:
            V = array(V)
            y = array(y)
        return sequence_length, V, y

    @staticmethod
    def samples_from_data(data, n_in, n_out, y_vector=True, np=False):
        _data = data
        X = []
        y = []
        for i in range(0, len(_data)):
            try:
                in_data = _data[i:i + n_in]
                out_data = _data[i + n_in + 1: i + n_in + n_out + 1]
                if len(out_data) < n_out:
                    break
                X.append(in_data)
                y.append(out_data)
            except Exception as e:
                print(e)
                break
        if y_vector:
            y = Sample.y_to_vector(y)
        if np:
            X = array(X)
            y = array(y)
        return X, y

    def attribute_samples(self, fips, city, fields, n_in, n_out, y_vector=True, np=False):
        X, y = self.samples(fips, n_in, n_out, y_vector=y_vector)
        print("aaa")
        if fields is not None:
            constants = city.attributes(fips, fields)
            V = Sample.sample_insert(X, constants)
        else:
            V = X
        sequence_length = len(V[0])
        if np:
            V = array(V)
            y = array(y)
        return sequence_length, V, y

    def daily_change(self):
        change = self.data.copy()
        for fips in change.columns:
            tmp = 0
            for date in change.index:
                v = change.at[date, fips]
                change.at[date, fips] = v - tmp
                tmp = v
        return change

    @staticmethod
    def Xy_partition(X, y, n):
        size = len(X)
        X_tarin = X[0:size - n]
        y_train = y[0:size - n]
        X_predict = X
        y_predict = y
        return (X_tarin, y_train), (X_predict, y_predict)

    def partition(self, i):
        if i > 0:
            return self.data[0:i], self.data[i + 1:]
        else:
            n = len(self.data)
            return self.data[0:n + i], self.data[i:]

    def partition_by_fraction(self, v, first, second):
        """

        :param v:
        :param first:
        :param second:
        :return:
        """
        if first + second != 100:
            raise ValueError("first + second must be 100")
        i = int(round(len(v) * first / 100))
        if i > 0:
            return self.data[0:i], self.data[i + 1:]
        else:
            n = len(self.data)
            return self.data[0:n + i], self.data[i:]

    def samples(self, fips, n_in, n_out, y_vector=True, np=False):
        _data = self.get(fips)
        return Sample.samples_from_data(_data, n_in, n_out, y_vector=y_vector, np=np)

    @staticmethod
    def y_to_vector(y):
        _result = []
        for e in y:
            _result.append(e[0])
        return _result

    @staticmethod
    def sample_insert(X, v):
        V = []
        for row in X:
            row = pd.Series(v).append(row)
            V.append(row)
        return V

class Source:

    directory = "../data"

class Cases(Sample):

    def __init__(self, name="Covid19-cases-110USCities.csv", data=None):
        Data.__init__(self)
        self.directory = Source.directory
        self.name = name
        self.kind = "Cases"

        if data is not None:
            self.data = data
        else:
            self.load(self.name)
        self.dates = self.data.columns

    def hopkins(self,
                name="COVID-19/csse_covid_19_data/csse_covid_19_time_series/"
                     "time_series_covid19_confirmed_US.csv"):
        self.directory = Source.directory
        self.name = name

        self.load(self.name)


class Death(Sample):

    def __init__(self, name="Covid19-deaths-110USCities.csv", data=None):
        Data.__init__(self)
        self.directory = Source.directory
        self.name = name
        self.kind = "Death"
        if data is not None:
            self.data = data
        else:
            self.load(self.name)
        self.dates = self.data.columns


class Mobility(Sample):

    def __init__(self, name="Covid19-USCities_Mobility.csv"):
        Data.__init__(self)
        self.directory = Source.directory
        self.name = name
        self.kind = "Mobility"
        self.load(self.name)
        self.dates = self.data.columns


class Who(Data):

    def __init__(self, directory="../../data/", sort=False):
        Data.__init__(self)
        self.name = f"{directory}/who_covid_19_sit_rep_time_series.csv"
        self.load()
        self.dates = self.data.columns
        self.complete = self.data.copy().transpose()
        # self._fix_china_region()
        # self._drop_unused_columns()
        self.data = self.data[self.data["Province/States"].isnull()]
        self.data = self.data.drop(["Province/States", "WHO region"], axis=1)
        self.data = self.data.rename(columns={'Country/Region': 'country'})
        self.data = self.data.set_index('country')
        if sort:
            self.data = self.data.sort_values(by=['country'])
        self.data = self.data.fillna(0)
        self.data = self.data.transpose()
        self.data = self.data.rename(columns={'United States of America': 'USA'})
        self.data = self.data.rename(columns={'The United Kingdom': 'UK'})
        self.names = list(self.data.columns)

    # def china_regions():
    #    regions = self.complete.dropna(subset=['Country/Region']).copy()
    #    regions = df[df["Province/States"].notnull()]

    def countries(self, names):
        return self.data[names]

    def load(self):
        self.data = pd.read_csv(self.name)


class Data110:
    
    def __init__(self, data=None):                 
        Data.__init__(self)
        self.directory = Source.directory
        self.name = "Covid19-cases-110USCities.csv"

        self.kind = "Cases"

        if data is not None:
            self.data = data
        else:
            self.load(self.name)
        self.dates = self.data.columns

        
    def hopkins(self, name="time_series_covid19_confirmed_US.csv"):
        self.directory = Source.directory
        self.name = name

        self.load(self.name)
        
        
        
    
    @staticmethod
    def read(directory="../data",
             start_date=None,
             end_date="2500-01-01"):

        try:
            cases = Data110.read_data(f"{directory}/Covid19-cases-110USCities.csv")
            death = Data110.read_data(f"{directory}/Covid19-deaths-110USCities.csv")
            city = Data110.read_data(f"{directory}/Static_315USCities_Pop.csv")
        except FileNotFoundError as e:
            Console.error("The file can not be found")
            print(e)

        columns = cases.columns
        city = city.loc[:, columns]
        death = death.loc[:, columns]

        if start_date is not None:
            # start_remove = pd.to_datetime(start_date)
            # end_remove = pd.to_datetime(end_date)
            cases = TsDate.between(cases, start_date, end_date)
            death = TsDate.between(death, start_date, end_date)

        return cases, death, city

    @staticmethod
    def read_data(filename,
                  start_date=None,
                  end_date="2500-01-01",
                  verbose=True):
        _fips_time = ["Covid19-deaths-110USCities.csv",
                      "Covid19-cases-110USCities.csv"]

        _population = ["Static_315USCities_Pop.csv"]

        _mobility = ["Covid19-USCities_Mobility.csv"]

        fips_time = any(x in filename for x in _fips_time)
        population = any(x in filename for x in _population)
        mobility = any(x in filename for x in _mobility)


        directory = Source.directory
        print (directory)
        filename = f"{directory}/{filename}"

        if verbose:
            print(f"Reading: {filename}")


        df = pd.read_csv(filename)

        if fips_time:
            df = df.set_index('FIPS')
            df.columns = pd.to_datetime(df.columns)
            df = df.transpose()
        elif population:
            df = df.set_index('FIPS')
            df = df.transpose()
        elif mobility:
            df = df.set_index('FIPS')
            df.columns = pd.to_datetime(df.columns)
            df = df.transpose()

        if start_date is not None:
            df = TsDate.between(df, start_date, end_date)

        return df


class Csse:
    """
    Downloads the Hopkins, Who, Europe data into a data directory
    """

    _repo = \
        "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/" \
        "master/csse_covid_19_data/csse_covid_19_time_series/"

    _who = \
        "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/" \
        "who_covid_19_situation_reports/who_covid_19_sit_rep_time_series/" \
        "who_covid_19_sit_rep_time_series.csv"

    _data = [
        "time_series_covid19_confirmed_US.csv",
        "time_series_covid19_confirmed_global.csv",
        "time_series_covid19_deaths_US.csv",
        "time_series_covid19_deaths_global.csv",
        "time_series_covid19_recovered_global.csv"
    ]

    @staticmethod
    def get_europe_data(directory):
        try:
            repo = "https://github.com/CSSEGISandData/COVID-19.git"
            os.system(f"cd {directory}; git clone {repo}")
        except:
            pass
        try:
            os.system(f"cd {directory}/COVID-19; git pull")
        except:
            pass

    @staticmethod
    def download(directory, verbose=False, force=False):
        data = [f"{Csse._repo}/{entry}" for entry in Csse._data]
        data.append(Csse._who)

        for source in data:
            filename = os.path.basename(source)
            destination = f"{directory}/{filename}"
            print(f"Download {filename}")
            if verbose:
                print("Source", source)
                print("Destination", destination)
            file_download(source, destination, force=force)
            print()

    @staticmethod
    def read_hopkins(directory="../data", name=None):
        """
        reads in the hopkins data and converts it to a
        FIPS(column), time(row) dataframe

        :param directory:
        :param name:
        :return: df
        """
        if name is None:
            name = f"{directory}/COVID-19/csse_covid_19_data/csse_covid_19_time_series/"\
                   "time_series_covid19_confirmed_US.csv"
        else:
            name = f"{directory}/{name}"
        df = pd.read_csv(name)
        df = df.drop(columns=['UID', 'iso2', 'iso3', 'code3',
                              'Admin2', 'Province_State',
                              'Country_Region', 'Lat', 'Long_',
                              'Combined_Key'])
        nan = df[df['FIPS'].isnull()].index.to_list()
        df = df.drop(nan)
        df['FIPS'] = df['FIPS'].astype(int)
        df = df.set_index('FIPS')
        df.columns = pd.to_datetime(df.columns)
        df = df.transpose()
        df = df.loc[:, (df != 0).any(axis=0)]
        return df

    @staticmethod
    def read_geocodes(directory, filename="all-geocodes-v2018.xlsx"):
        location = f"{directory}/{filename}"
        print(f"Reading Geocodes: {location}")
        df = pd.read_excel(location, skiprows=range(0, 4))
        df.rename(columns={
            "Summary Level": "Summary",
            "State Code (FIPS)": "State",
            "County Code (FIPS)": "County",
            "County Subdivision Code (FIPS)": "Subdivision",
            "Place Code (FIPS)": "FIPS",
            "Consolidated City Code (FIPS)": "City",
            "Area Name (including legal/statistical area description)": "Name"
        }, inplace=True)
        return df


if __name__ == "__main__":
    source = Csse()
    source.download("data2")
