"""
Generic, configurable scatterplot
"""
import collections
import warnings

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns


class PlottingAttribute(object):

    __slots__ = 'groupby', 'title', 'palette', 'group_to_attribute'

    def __init__(self, groupby, title, palette, order):
        """An attribute that you want to visualize with a specific visual cue

        Parameters
        ----------
        groupby : mappable
            A series or dict or list to groupby on the rows of the data
        title : str
            Title of this part of the legend
        palette : list-like
            What to plot for each group
        """
        self.groupby = groupby
        self.title = title
        self.palette = palette
        if order is not None:
            # there's more than one attribute
            self.group_to_attribute = dict(zip(order, palette))
        else:
            # There's only one attribute
            self.group_to_attribute = {None: palette[0]}

    def __getitem__(self, item):
        return self.group_to_attribute[item]


class PlotterMixin(object):

    """
    Must be mixed with something that creates the ``self.plot_data`` att
ribute


    Attributes
    ----------

    color :



    """
    # Markers that can be filled, in a reasonable order so things that can be
    # confu
sed with each other (e.g. triangles pointing to the left or right) are
    # not next to each other
    filled_markers = (u'o', u'v', u's', u'*', u'h', u'<', u'H', u'x', u'8',
                      u'>', u'D', u'd', u'^')
    linewidth_min, linewidth_max = 0.1, 5

    alpha_min, alpha_max = 0.1, 1

    size_min, size_max = 3, 30

    legend_order = 'color', 'symbol', 'linewidth', 'edgecolor', 'alpha', 'size'

    def establish_colors(self, color, hue, hue_order, palette):
        """Get a list of colors for the main component of the plots."""
        n_colors = None

        current_palette = sns.utils.get_color_cycle()


        color_labels = None
        color_title = None


        if color is not None and palette is not None:
            error = 'Cannot interpret colors to plot when both "color" and ' \
                    '"palette" are specified'
            raise ValueError(error)


        # Force "hue" to be a mappable
        if hue is not None:
            try:
                # Check if "hue" is a column in the data
                color_title = str(hue)
                hue = self.data[hue]
            except (ValueError, KeyError):
                # Hue is already a mappable
                if isinstance(hue, pd.Series):
                    color_title = hue.name
                else:
                    color_title = None

            # This will give the proper number of categories even if there are
            #  more categories in "hue_order" than represented in "hue"
            hue_order = sns.utils.categorical_order(hue, hue_order)
            color_labels = hue_order
            hue = pd.Categorical(hue, hue_order)
            n_colors = len(self.plot_data.groupby(hue))
        else:
            if hue_order is not None:

                # Check if "hue_order" specifies rows in the data
                samples_to_plot = self.plot_data.index.intersection(hue_order)
                n_colors = len(samples_to_plot)
                if n_colors > 0:
                    # Different color for every sample (row name)
                    hue = pd.Series(self.plot_data.index,
                                    index=self.plot_data.index)

                else:
                    error = "When 'hue=None' and 'hue_order' is specified, " \
                            "'hue_order' must overlap with the data row " \
                            "names (index)"
                    raise ValueError(error)
            else:
                # Same color for everything
                hue = pd.Series('hue', index=self.plot_data.index)
                n_colors = 1

        if palette is not None:
            colors = sns.color_palette(palette, n_colors=n_colors)
        elif color is not None:
            colors = sns.light_palette(color, n_colors=n_colors)
        else:
            colors = sns.light_palette(current_palette[0],
                                       n_colors=n_colors)
        self.color = PlottingAttribute(hue, color_title, colors, hue_order)

    def _maybe_make_grouper(self, attribute, palette_maker, order=None,
                            func=None, default=None):
        """Create a Series from a single attribute, else make categorical

        Checks if the attribute is in the data provided, or is an external
        mapper

        Parameters
        ----------
        attribute : object
            Either a single item to create into a series, or a series mapping
            each sample to an attribute (e.g. the plotting symbol 'o' or
            linewidth 1)
        palette_maker : function
            Function which takes an integer and creates the appropriate
            palette for the attribute, e.g. shades of grey for edgecolor or
            linearly spaced sizes
        order : list
            The order to create the attributes into
        func : function
            A function which returns true if the attribute is a single valid
            instance, e.g. "black" for color or 0.1 for linewidth. Otherwise,
            we assume that "attribute" is a mappable

        Returns
        -------
        grouper : pandas.Series
            A mapping of the high dimensional data samples to the attribute
        """

        title = None

        if func is None or func(attribute):
            # Use this single attribute for everything
            return PlottingAttribute(pd.Series(None, index=self.samples),
                                     title, (attribute,), order)
        else:

            try:
                # Check if this is a column in the data
                attribute = self.data[attribute]
            except (ValueError, KeyError):
                pass

            if isinstance(attribute, pd.Series):
                title = attribute.name
            order = sns.utils.categorical_order(attribute, order)

            palette = palette_maker(len(order))
            attribute = pd.Categorical(attribute, categories=order,
                                       ordered=True)
            return PlottingAttribute(pd.Series(attribute, index=self.samples),
                                     title, palette, order)

    def establish_symbols(self, marker, marker_order, text, text_order):
        """Figure out what symbol put on the axes for each data point"""

        symbol_title = None

        if isinstance(text, bool):
            # Option 1: Text is a boolean
            if text:
                # 1a: text=True, so use the sample names of data as the
                # plotting symbol
                symbol_title = 'Samples'
                symbols = [str(x) for x in self.samples]
                symbol = pd.Series(self.samples, index=self.samples)
            else:
                # 1b: text=False, so use the specified marker for each sample
                symbol = self._maybe_make_grouper(marker, marker_order, str)
                if marker is not None:
                    try:
                        symbol_title = marker
                        symbol = self.data[marker]
                        symbols = sns.categorical_order(symbol, marker_order)
                    except (ValueError, KeyError):
                        # Marker is a single marker, or already a groupable
                        if marker in self.filled_markers:
                            # Single marker so make a tuple so it's indexable
                            symbols = (marker,)
                        else:
                            # already a groupable object
                            if isinstance(marker, pd.Series):
                                symbol_title = marker.name
                            n_symbols = len(self.plot_data.groupby(symbol))
                            if n_symbols > len(self.filled_markers):
      