import numpy as np
import pandas as pd


# noinspection PyPep8Naming
class Date:

    @staticmethod
    def days(time_start, time_end):
        """
        number of days between start and enddate

        :param time_start: string
        :param time_end: string
        :return:
        """
        t0 = pd.to_datetime(time_start)
        t1 = pd.to_datetime(time_end)
        days = (t1 - t0).days
        return days + 1

    # fix test
    @staticmethod
    def rangeD(time_start, time_end):
        """
        a pandas time series between start and enddate

        :param time_start: string
        :param time_end: string
        :return:
        """
        t0 = pd.to_datetime(time_start)
        d = Date.days(time_start, time_end)
        series = t0 + pd.to_timedelta(np.arange(d), 'D')
        return series.to_period('D')

    # fix
    # fix test
    @staticmethod
    def rangeS(time_start, time_end):
        """
        a pandas time series between start and enddate

        :param time_start: string
        :param time_end: string
        :return:
        """
        s = Date.rangeD(time_start, time_end)
        return [str(t) for t in s]

    @staticmethod
    def rangeN(time_start, time_end, zero=None):
        """
        the day number between start and and date.
        If zero is specified, this is the first day

        :param time_start: string
        :param time_end: string
        :param zero: string
        :return:
        """
        if zero is None:
            zero = time_start
        dt0 = Date.days(zero, time_start)
        dt = Date.days(time_start, time_end)
        v = range(0, dt)
        t = [r + dt0 for r in v]
        return t

    @staticmethod
    def get_date(in_date, kind='date'):
        """
        Method accepts string/series/array containing date values. Returns
        date, string or datetime based on out_type

        Note: datetime is currently in pandas._libs.tslibs.timestamps.Timestamp
              format
        :param in_date: string/series/array containing date values
        :param kind: date to get a python date object,
                        strdate to get date in string format,
                        datetime to get datetime object,
                        strdatetime to get datetime in string format
        :return: date/datetime or string
        """
        out_series = pd.to_datetime(pd.Series(in_date),
                                    infer_datetime_format=True)
        if kind == 'date':
            return out_series.apply(lambda x: x.date())
        elif kind == 'strdate':
            return out_series.apply(lambda x: str(x.date()))
        elif kind == 'datetime':
            return out_series.apply(lambda x: x.to_pydatetime())
        elif kind == 'strdatetime':
            return out_series.apply(lambda x: str(x.to_pydatetime()))
        else:
            raise NotImplementedError

    # not tested
    # At minimum a string such as "2020-03-26", "2050-01-24",
    @staticmethod
    def remove(df, start_date, end_date):
        """
        removes from the df all rows that are between the dates

        :param df: assumes a df with date as index
        :param start_date:
        :param end_date:
        :return:
        """
        r = df.loc[(df.index <= start_date) | (df.index >= end_date)]
        return r

    # not tested
    # At minimum a string such as "2020-03-26", "2050-01-24",
    @staticmethod
    def between(df, start_date, end_date):
        """
        returns a df with all dates between start_date and end_date.

        :param df: assumes a df with date as index
        :param start_date:
        :param end_date:
        :return:
        """
        # r = df.loc[(df.index >= start_date) & (df.index <= end_date)]

        start_date = Date.get_date(start_date)[0]
        end_date = Date.get_date(end_date)[0]

        r = df.loc[(df.index.date >= start_date) & (df.index.date <= end_date)]
        return r

    @staticmethod
    def get_time_period(df,
                        start_date="1999-01-01",
                        end_date="2050-01-01",
                        inplace=False):
        """
        returns form the dataframe values between start and end date

        :param df:
        :param start_date: string
        :param end_date: string
        :param inplace: True if the df is directly reduced without copy
        :return: a dataframe with selected dates
        """
        if inplace:
            select = df
        else:
            select = df.copy()
        t0 = pd.to_datetime(start_date)
        t1 = pd.to_datetime(end_date)
        select = select.loc[
            (select.index >= start_date) & (select.index <= end_date)]
        return t0, t1, select

