from pyFTS.common import FuzzySet, Membership
import numpy as np
import matplotlib.pylab as plt


class Partitioner(object):
    """
    Universe of Discourse partitioner. Split data on several fuzzy sets
    """

    def __init__(self, **kwargs):
        """
        Universe of Discourse partitioner scheme. Split data on several fuzzy sets
        """
        self.name = kwargs.get('name',"")
        """partitioner name"""
        self.partitions = kwargs.get('npart', 10)
        """The number of universe of discourse partitions, i.e., the number of fuzzy sets that will be created"""
        self.sets = {}
        self.membership_function = kwargs.get('func', Membership.trimf)
        """Fuzzy membership function (pyFTS.common.Membership)"""
        self.setnames = kwargs.get('names', None)
        """list of partitions names. If None is given the partitions will be auto named with prefix"""
        self.prefix = kwargs.get('prefix', 'A')
        """prefix of auto generated partition names"""
        self.transformation = kwargs.get('transformation', None)
        """data transformation to be applied on data"""
        self.indexer = kwargs.get('indexer', None)
        self.variable = kwargs.get('variable', None)
        """In a multivariate context, the variable that contains this partitioner"""
        self.type = kwargs.get('type', 'common')
        """The type of fuzzy sets that are generated by this partitioner"""
        self.extractor = kwargs.get('extractor', lambda x: x)
        """Anonymous function used to extract a single primitive type from an object instance"""
        self.ordered_sets = None
        """A ordered list of the fuzzy sets names, sorted by their middle point"""

        if kwargs.get('preprocess',True):

            data = kwargs.get('data',[None])

            if self.indexer is not None:
                ndata = self.indexer.get_data(data)
            else:
                ndata = data

            if self.transformation is not None:
                ndata = self.transformation.apply(ndata)
            else:
                ndata = data

            if self.indexer is not None:
                ndata = self.indexer.get_data(ndata)

            _min = np.nanmin(ndata)
            if _min == -np.inf:
                ndata[ndata == -np.inf] = 0
                _min = np.nanmin(ndata)

            self.min = float(_min * 1.1 if _min < 0 else _min * 0.9)

            _max = np.nanmax(ndata)
            self.max = float(_max * 1.1 if _max > 0 else _max * 0.9)

            self.sets = self.build(ndata)

            if self.ordered_sets is None and self.setnames is not None:
                self.ordered_sets = self.setnames[:len(self.sets)]
            else:
                self.ordered_sets = FuzzySet.set_ordered(self.sets)

            del(ndata)

    def build(self, data):
        """
        Perform the partitioning of the Universe of Discourse

        :param data:  training data
        :return: 
        """
        pass

    def get_name(self, counter):
        """
        Find the name of the fuzzy set given its counter id.

        :param counter: The number of the fuzzy set
        :return: String
        """
        return self.prefix + str(counter) if self.setnames is None else self.setnames[counter]

    def lower_set(self):
        """
        Return the fuzzy set on lower bound of the universe of discourse.

        :return: Fuzzy Set
        """
        return self.sets[self.ordered_sets[0]]

    def upper_set(self):
        """
        Return the fuzzy set on upper bound of the universe of discourse.

        :return: Fuzzy Set
        """
        return self.sets[self.ordered_sets[-1]]

    def fuzzyfy(self, data, **kwargs):
        return FuzzySet.fuzzyfy(data, self, **kwargs)

    def plot(self, ax, rounding=0):
        """
        Plot the partitioning using the Matplotlib axis ax

        :param ax: Matplotlib axis
        """
        ax.set_title(self.name)
        ax.set_ylim([0, 1.1])
        ax.set_xlim([self.min, self.max])
        ticks = []
        x = []
        for key in self.sets.keys():
            s = self.sets[key]
            if s.type == 'common':
                self.plot_set(ax, s)
            elif s.type == 'composite':
                for ss in s.sets:
                    self.plot_set(ax, ss)
            ticks.append(str(round(s.centroid,rounding))+'\n'+s.name)
            x.append(s.centroid)
        ax.xaxis.set_ticklabels(ticks)
        ax.xaxis.set_ticks(x)

    def plot_set(self, ax, s):
        """
        Plot an isolate fuzzy set on Matplotlib axis

        :param ax: Matplotlib axis
        :param s: Fuzzy Set
        """
        if s.mf == Membership.trimf:
            ax.plot([s.parameters[0], s.parameters[1], s.parameters[2]], [0, s.alpha, 0])
        elif s.mf in (Membership.gaussmf, Membership.bellmf, Membership.sigmf):
            tmpx = np.linspace(s.lower, s.upper, 100)
            tmpy = [s.membership(kk) for kk in tmpx]
            ax.plot(tmpx, tmpy)
        elif s.mf == Membership.trapmf:
            ax.plot(s.parameters, [0, s.alpha, s.alpha, 0])
        elif s.mf == Membership.singleton:
            ax.plot([s.parameters[0],s.parameters[0]], [0, s.alpha])

    def __str__(self):
        """
        Return a string representation of the partitioner, the list of fuzzy sets and their parameters

        :return:
        """
        tmp = self.name + ":\n"
        for key in self.sets.keys():
            tmp += str(self.sets[key])+ "\n"
        return tmp

    def __len__(self):
        """
        Return the number of partitions

        :return: number of partitions
        """
        return self.partitions
