import os
from functools import lru_cache
from collections import deque
from skimage.color import rgb2gray
from skimage.io import imread
from random import random
import scipy as sp
import numpy as np

class DataProducerMixin:
    def get_data(self, nr_samples_per_class: int, nr_classes: int, sample_length: int, sample_distance: int,
                 subsampling=1):
        """

        Parameters
        ----------
        nr_samples_per_class: Nr of time series per class
        nr_classes: Number of different classes
        sample_length: The length of a single time series
        sample_distance: The temporal distance of the time series starts

        Returns
        -------

        X, y: Time series, class label
            X is nr_samples_per_class * nr_classes x sample_length matrix
            y is a nr_samples_per_class * nr_classes  x 1 matrix if integer class labels
        """
        y = []
        X = []
        for cls, data in enumerate(self):
            assert sample_distance * nr_samples_per_class < len(data), "Data not big enough for so many samples"
            if cls < nr_classes:
                for sample_start in range(0, sample_distance*nr_samples_per_class, sample_distance):
                    y.append(cls)
                    X.append(data[sample_start:sample_start+sample_length])
        y = sp.array(y)
        X = sp.ascontiguousarray(sp.array(X)[:,::subsampling])
        return X, y


class TextureData(DataProducerMixin):
    def __init__(self):
        self.folder = os.path.join(os.path.dirname(__file__), "textures")
        self.image_names = sorted(img for img in os.listdir(self.folder) if img.lower()[-3:] in ["jpg", "png"])

    def __len__(self):
        return len(self.image_names)

    def __getitem__(self, image_nr):
        return self.load(image_nr)

    @property
    def identifiers(self):
        return self.image_names

    def load_grayscale_image_normalized(self, image_nr):
        gray_image = self._load_grayscale_image(image_nr)
        gray_image -= gray_image.min()
        gray_image /= gray_image.max()
        return gray_image

    @lru_cache(40)
    def _load_grayscale_image(self, image_nr) -> np.ndarray:
        color_image = imread(os.path.join(self.folder, self.image_names[image_nr]))
        gray_image = rgb2gray(color_image)
        return gray_image

    def load(self, image_nr):
        gray_image = self.load_grayscale_image_normalized(image_nr)
        for line in range(0, gray_image.shape[0], 2):
            gray_image[line,:] = gray_image[line,::-1]
        gray_image = gray_image.flatten()
        return gray_image

    def __repr__(self):
        return '<{} folder={}, {} images>'.format(type(self).__name__, self.folder, len(self))


class Glass(DataProducerMixin):
    dt = .1
    beta = 2
    gamma = 1
    n = 9.65

    def __init__(self, length=100000, taus=(5, 10)):
        self.length = length
        self.taus = taus

    @property
    def identifiers(self):
        return self.taus

    def __repr__(self):
        return "<Mackey-Glass series taus={}".format(self.taus)

    def __len__(self):
        return len(self.taus)

    def __getitem__(self, item):
        return self._generate_sequence(int(self.taus[item] / self.dt))

    @lru_cache()
    def _generate_sequence(self, tau):
        history = deque([random() * 1 + .5]*tau, tau)
        sequence = []
        x = 1

        for _ in range(self.length):
            x = self._step(x, history.popleft())
            sequence.append(x)
            history.append(x)

        sequence = sp.asarray(sequence)
        sequence -= sequence.mean()
        return sequence

    def _step(self, x_last, x_tau):
        return x_last + self.dt * (-self.gamma * x_last + self.beta * x_tau / (1 + x_tau**self.n))
