import matplotlib.pyplot as plt
import numpy as np
import pandas
# noinspection PyUnresolvedReferences
from numpy import array
from numpy import concatenate
import random


class TS:

    def __init_(self, t):
        self.t = t


# noinspection PyPep8Naming
class Sample:

    @staticmethod
    def data(n):
        """
        returns a data sample vector from 0, ...,n-1
        :param n: length of the data sample
        :return: list
        """
        return list(range(0, n))

    @staticmethod
    def vector_insert(X, v):
        V = []
        for row in X:
            row = np.array(v + row.tolist())
            V.append(row)
        return V

    @staticmethod
    # noinspection PyPep8Naming
    def sample_add(X, Y):
        return np.concatenate((X, Y), axis=1)

    @staticmethod
    def sample_insert(X, v):
        V = []
        for row in range(0, X.shape[0]):
            V.append(v)
        X = Sample.sample_add(V, X)
        return X

    @staticmethod
    def sample_merge(X1, y1, X2, y2):
        X = array(list(X1) + list(X2))
        y = array(list(y1) + list(y2))
        return X, y

    @staticmethod
    def sample_data_merge(data1, data2, input_length):
        X1, y1 = example(data1, input_length)
        X2, y2 = example(data2, input_length)
        X = array(list(X1) + list(X2))
        y = array(list(y1) + list(y2))
        return X, y

    @staticmethod
    def insert_fields(city, fips, fields, X, y, np=True):
        if fields is not None:
            constants = city.attributes(fips, fields)
            V = Sample.vector_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 print_samples(X, y, n=3):
        print()
        print("Samples")
        for i in range(len(X)):
            if i < n or i > len(X) - n - 1:
                print(X[i], y[i])
            if i == n:
                print("...")

    @staticmethod
    def generate_in_out_sequence(sequence, n_steps_in, n_steps_out):
        X, y = list(), list()
        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)
        return array(X), array(y)

    @staticmethod
    def generate_in_out_vector(sequence=None, n_steps=3):
        """

        :param sequence: [10, 20, 30]
        :param n_steps:
        :return:
        """
        X, y = generate_in_out_sequence(sequence, n_steps, 1)
        y = concatenate(y)
        return array(X), array(y)

    @staticmethod
    def example(data, input_length):
        return generate_in_out_vector(data, n_steps=input_length)

    @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)

    @staticmethod
    def Xy_shuffle(X, y, seed=0):
        """
        shuffles the (X, y) randomly, while keeping the i-th
        element associated

        :param X: X vector
        :param y: y vector
        :param seed: not yet implemented
        :return:
        """

        data = [(X[i], y[i]) for i in range(0, len(y))]
        for i in range(0, len(y)):
            random.shuffle(data)

        X_r = [data[i][0] for i in range(0, len(y))]
        y_r = [data[i][1] for i in range(0, len(y))]

        return X_r, y_r

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

    @staticmethod
    def partition_by_fraction(data, v, first, second):
        """

        :param data:
        :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 data[0:i], data[i + 1:]
        else:
            n = len(data)
            return data[0:n + i], data[i:]
