"Supporting objects and functions to convert Matplotlib objects into Bokeh."
#-----------------------------------------------------------------------------
# Copyright (c) 2012 - 2014, Continuum Analytics, Inc. All rights reserved.
#
# Powered by the Bokeh Development Team.
#
# The full license is in the file LICENSE.txt, distributed with this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

from __future__ import absolute_import

import itertools
import warnings

import matplotlib as mpl
import numpy as np
import pandas as pd
from six import string_types

from ..models import (ColumnDataSource, FactorRange, DataRange1d, DatetimeAxis, GlyphRenderer,
                     Grid, GridPlot, LinearAxis, Plot, CategoricalAxis, Legend)
from ..models.glyphs import (Asterisk, Circle, Cross, Diamond, InvertedTriangle,
                            Line, MultiLine, Patches, Square, Text, Triangle, X)
from ..plotting import DEFAULT_TOOLS
from ..plotting_helpers import _process_tools_arg

from .mplexporter.renderers import Renderer
from .mpl_helpers import convert_dashes, get_props_cycled, is_ax_end, xkcd_line

#-----------------------------------------------------------------------------
# Classes and functions
#-----------------------------------------------------------------------------

class BokehRenderer(Renderer):

    def __init__(self, pd_obj, xkcd):
        "Initial setup."
        self.fig = None
        self.pd_obj = pd_obj
        self.xkcd = xkcd
        self.zorder = {}
        self.handles = {}

    def open_figure(self, fig, props):
        "Get the main plot properties and create the plot."
        self.width = int(props['figwidth'] * props['dpi'])
        self.height = int(props['figheight'] * props['dpi'])
        self.plot = Plot(x_range=DataRange1d(),
                         y_range=DataRange1d(),
                         plot_width=self.width,
                         plot_height=self.height)

    def close_figure(self, fig):
        "Complete the plot: add tools."
        # Add tools
        tool_objs = _process_tools_arg(self.plot, DEFAULT_TOOLS)
        self.plot.add_tools(*tool_objs)

        # Simple or Grid plot setup
        if len(fig.axes) <= 1:
            self.fig = self.plot
            self.plot.renderers.sort(key=lambda x: self.zorder.get(x._id, 0))
        else:
            # This list comprehension splits the plot.renderers list at the "marker"
            # points returning small sublists corresponding with each subplot.
            subrends = [list(x[1]) for x in itertools.groupby(
                        self.plot.renderers, lambda x: is_ax_end(x)) if not x[0]]
            plots = []
            for i, axes in enumerate(fig.axes):
                # create a new plot for each subplot
                _plot = Plot(x_range=self.plot.x_range,
                             y_range=self.plot.y_range,
                             plot_width=self.width,
                             plot_height=self.height)

                _plot.title = ""
                # and add new tools
                _tool_objs = _process_tools_arg(_plot, DEFAULT_TOOLS)
                _plot.add_tools(*_tool_objs)
                # clean the plot ref from axis and grids
                _plot_rends = subrends[i]
                for r in _plot_rends:
                    if not isinstance(r, GlyphRenderer):
                        r.plot = None
                # add all the renderers into the new subplot
                for r in _plot_rends:
                    if isinstance(r, GlyphRenderer):
                        _plot.renderers.append(r)
                    elif isinstance(r, Grid):
                        _plot.add_layout(r)
                    else:
                        if r in self.plot.below:
                            _plot.add_layout(r, 'below')
                      
  elif r in self.plot.above:
                            _plot.add_layout(r, 'above')
                        elif r in self.plot.left:
                            _plot.add_layout(r, 'left')
       
                 elif r in self.plot.right:
                            _plot.add_layout(r, 'right')

                _plot.renderers.sort(key=lambda x: self.zorder.get(x._id, 0))
                plots.append(_plot)
            (a, b, c) = fig.axes[0].get_geometry()
            p = np.array(plots)
            n = np.resize(p, (a, b))
            grid = GridPlot(children=n.tolist())
            self.fig = grid

    def open_axes(self, ax, props):
        "Get axes data and create the axes and grids"
        # Get axes, title and grid into class attributes.
        self.ax = ax
        self.plot.title = ax.get_title()
        # to avoid title conversion by draw_text later
        
        #Make sure that all information about the axes are passed to the properties
        if props.get('xscale', False):
            props['axes'][0]['scale'] = props['xscale']
            
        if props.get('yscale', False):
            props['axes'][1]['scale'] = props['yscale']
            
        # Add axis
        for props in props['axes']:
            if   props['position'] == "bottom" : location, dim, thing = "below", 0, ax.xaxis
            elif props['position'] == "top"    : location, dim, thing = "above", 0, ax.xaxis
            else: location, dim, thing = props['position'], 1, ax.yaxis

            baxis = self.make_axis(thing, location, props)

            if dim==0:
                gridlines = ax.get_xgridlines()
            else:
                gridlines = ax.get_ygridlines()

            if gridlines:
                self.make_grid(baxis, dim, gridlines[0])

    def close_axes(self, ax):
        "Complete the axes adding axes-dependent plot props"
        background_fill_color = ax.get_axis_bgcolor()
        if background_fill_color == 'w':
            background_fill_color = 'white'
        self.plot.background_fill_color = background_fill_color
        if self.xkcd:
            self.plot.title_text_font = "Comic Sans MS, Textile, cursive"
            self.plot.title_text_font_style = "bold"
            self.plot.title_text_color = "black"

        # Add a "marker" Glyph to help the plot.renderers splitting in the GridPlot build
        dummy_source = ColumnDataSource(data=dict(name="ax_end"))
        self.plot.renderers.append(GlyphRenderer(data_source=dummy_source, glyph=X()))

    def open_legend(self, legend, props):
        lgnd = Legend(location="top_right")
        try:
            for label, obj in zip(props['labels'], props['handles']):
                lgnd.legends.append((label, [self.handles[id(obj)]]))
            self.plot.add_layout(lgnd)
        except KeyError:
            pass

    def close_legend(self, legend):
        pass

    def draw_line(self, data, coordinates, style, label, mplobj=None):
        "Given a mpl line2d instance create a Bokeh Line glyph."
        _x = data[:, 0]
        if self.pd_obj is True:
            try:
                x = [pd.Period(ordinal=int(i), freq=self.ax.xaxis.freq).to_timestamp() for i in _x]
            except AttributeError: #  we probably can make this one more intelligent later
                x = _x
        else:
            x = _x

        y = data[:, 1]
        if self.xkcd:
            x, y = xkcd_line(x, y)

        line = Line()
        source = ColumnDataSource()
        line.x = source.add(x)
        line.y = source.add(y)

        line.line_color = style['color']
        line.line_width = style['linewidth']
        line.line_alpha = style['alpha']
        line.line_dash = [] if style['dasharray'] is "none" else [int(i) for i in style['dasharray'].split(",")]  # str2list(int)
        # line.line_join = line2d.get_solid_joinstyle() # not in mplexporter
        # line.line_cap = cap_style_map[line2d.get_solid_capstyle()] # not in mplexporter
        if self.xkcd:
            line.line_width = 3

        r = self.plot.add_glyph(source, line)
        self.zorder[r._id] = style['zorder']
        se