#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
plot: SpacePy plotting routines

This package aims to make getting publication ready plots easier.
It provides classes and functions for different types of plot 
(e.g. Spectrogram, levelPlot), for helping make plots more cleanly 
(e.g. set_target, dual_half_circle), and for making plots convey 
information more cleanly, with less effort 
(e.g. applySmartTimeTicks, style).

This plot module now provides style sheets. For most standard plotting 
we recommend the *default* style sheet (aka *spacepy*). To auto-apply the
default plot style the following should be added to your spacepy.rc file::

    apply_plot_styles: True

Different plot types may not work well with this style, so we have provided
alternatives. For polar plots, spectrograms, or anything with larger blocks 
of color, it may be better to use one of the alternatives::

    import spacepy.plot as splot
    splot.style('altgrid') # inverts background from default so it's white
    splot.style('polar') # designed for filled polar plots
    splot.revert_style() # put the style back to matplotlib defaults

For those constrained by institutional computing, we also provide the
new colormaps developed for matplotlib v2. These colormaps are designed 
to be perceptually uniform, and hence colorblind-friendly.

Authors: Brian Larsen and Steve Morley
Institution: Los Alamos National Laboratory
Contact: balarsen@lanl.gov


Copyright 2011-2016 Los Alamos National Security, LLC.

.. autosummary::
   :template: clean_function.rst
   :toctree: autosummary

   add_logo
   annotate_xaxis
   applySmartTimeTicks
   available
   collapse_vertical
   dual_half_circle
   levelPlot
   plot
   revert_style
   set_target
   shared_ylabel
   solarRotationPlot
   Spectrogram
   style
   timestamp
   add_arrows

Most of the functionality in the plot module is made available directly 
through the *plot* namespace. However, the plot module does contain
several submodules listed below

.. autosummary::
    :toctree: autosummary
    :template: clean_module.rst

    carrington
    colourmaps
    spectrogram
    utils

"""
try:
    from collections.abc import Mapping
except ImportError:
    from collections import Mapping
import os
import warnings
import numpy as np
import matplotlib as mpl
from matplotlib.patches import Wedge
from matplotlib import __version__ as mplv
import matplotlib.pyplot as plt
from spacepy import __path__ as basepath
from spacepy.datamodel import dmcopy
import spacepy.datamanager as dman
from .. import config
from .spectrogram import *
from .utils import *
from .carrington import *
from .colourmaps import plasma as _plasma
from .colourmaps import plasma_r as _plasma_r
from .colourmaps import viridis as _viridis
from .colourmaps import viridis_r as _viridis_r

plt.register_cmap(name='plasma', cmap=_plasma)
plt.register_cmap(name='plasma_r', cmap=_plasma_r)
plt.register_cmap(name='viridis', cmap=_viridis)
plt.register_cmap(name='viridis_r', cmap=_viridis_r)

def plot(*args, **kwargs):
    '''Convenience wrapper for matplotlib's plot function

    As with matplotlib's plot function, *args* is a variable length
    argument, allowing for multiple *x*, *y* pairs, each with optional 
    format string. For full details, see matplotlib.pyplot.plot

    Other Parameters
    ----------------
    smartTimeTicks : boolean
        If True then use applySmartTimeTicks to set x-axis labeling
    figsize : array-like, 2 elements
        Set figure size directly on call to plot, (width, height)
    **kwargs : other keywords
        Other keywords to pass to matplotlib.pyplot.plot

    '''
    if 'smartTimeTicks' in kwargs:
        sTT = kwargs['smartTimeTicks']
        del kwargs['smartTimeTicks']
    else:
        sTT = False
    if 'figsize' not in kwargs:
        kwargs['figsize'] = (10,6)
        fig = plt.figure(figsize=kwargs['figsize'])
        del kwargs['figsize']
    pobj = plt.plot(*args, **kwargs)
    if sTT is True:
        ax = plt.gca()
        applySmartTimeTicks(ax, args[0], dolimit=True, dolabel=False)
    return pobj

def available(returnvals=False):
    '''List the available plot styles provided by spacepy.plot

    Note that some of the available styles have multiple aliases.
    To apply an available style, use `spacepy.plot.style`.
    '''
    spacepystyle = os.path.join('{0}'.format(basepath[0]), 'data', 'spacepy.mplstyle')
    spacepyaltstyle = os.path.join('{0}'.format(basepath[0]), 'data', 'spacepy_altgrid.mplstyle')
    polarstyle = os.path.join('{0}'.format(basepath[0]), 'data', 'spacepy_polar.mplstyle')
    lookdict = {'default': spacepystyle,
                'spacepy': spacepystyle,
                'spacepy_altgrid': spacepyaltstyle,
                'altgrid': spacepyaltstyle,
                'spacepy_polar': polarstyle,
                'polar': polarstyle
               }
    if returnvals:
        return lookdict
    else:
        return list(lookdict.keys())

def style(look=None, cmap='plasma'):
    '''
    Apply SpacePy's matplotlib style settings from a known style sheet.

    Parameters
    ----------
    look : str
    Name of style. For a list of available style names, see `spacepy.plot.available`.
    '''
    lookdict = available(returnvals=True)
    try:
        usestyle = lookdict[look]
    except KeyError:
        usestyle = lookdict['default']
    try:
        plt.style.use(usestyle)
    except AttributeError: #plt.style.use not available, old matplotlib?
        import warnings
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            dum = mpl.rc_params_from_file(usestyle)
            styapply = dict()
            #remove None values as these seem to cause issues...
            for key in dum:
                if dum[key] is not None: styapply[key] = dum[key]
            for key in styapply:
                mpl.rcParams[key] = styapply[key]
    mpl.rcParams['image.cmap'] = cmap

#save current rcParams before applying spacepy style
oldParams = dict()
for key, val in mpl.rcParams.items():
        oldParams[key] = dmcopy(val)
if config['apply_plot_styles']:
    style()

def revert_style():
    '''Revert plot style settings to those in use prior to importing spacepy.plot
    '''
    import warnings
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        for key in oldParams:
            try:
                mpl.rcParams[key] = oldParams[key]
            except ValueError:
                pass

def dual_half_circle(center=(0,0), radius=1.0,
                     sun_direction='right', ax=None, colors=('w','k'),
                     **kwargs):
    """
    Plot two half circles to a plot with the specified face colors and
    rotation. This is normal to use to denote the sun direction in
    magnetospheric science plots.

    Other Parameters
    ----------------
    center : array-like, 2 elements
        Center in data coordinates of the circles, default (0,0)
    radius : float
        Radius of the circles, defualt 1.0
    sun_direction : string or float
        The rotation direction of the first (white) circle. Options are ['down',
        'down right', 'right', 'up left', 'up right', 'up', 'down left', 'left']
        or an angle in degrees counter-clockwise from up. Default right.
    ax : matplotlib.axes
        Axis to plot the circles on.
    colors : array-like, 2 elements
        The two colors for the circle fill. The First number is the light and
        second is the dark.
    **kwargs : other keywords
        Other keywords to pass to matplotlib.patches.Wedge

    Returns
    -------
    out : tuple
        Tuple of the two wedge objects

    Examples
    --------
    >>> import spacepy.plot
    >>> spacepy.plot.dual_half_circle()

    """
    sun_dict = {'left':90,
                'right':-90,
                'up':0,
                'down':180,
                'up right':-45,
                'up left':45,
                'down right':45+180,
                'down left':-45+180,
                }
    try:
        angle = sun_dict[sun_direction]
    except KeyError:
        try:
            angle = float(sun_direction)
        except ValueError:
            raise(ValueError("Sun_direction was not understood, must be a float or {0}".format(sun_dict.keys())))
                
    theta1, theta2 = angle, angle + 180
    if ax is None:
        ax = plt.gca()
        
    w1 = Wedge(center, radius, theta1, theta2, fc=colors[0],transform=ax.transData._b, **kwargs)
    w2 = Wedge(center, radius, theta2, theta1, fc=colors[1], transform=ax.transData._b,**kwargs)
    for wedge in [w1, w2]:
        ax.add_artist(wedge)
    return (w1, w2)

def levelPlot(data, var=None, time=None, levels=(3, 5), target=None, colors=None, **kwargs):
    """
    Draw a step-plot with up to 5 levels following a color cycle (e.g. Kp index "stoplight")

    Parameters
    ----------
    data : array-like, or dict-like
        Data for plotting. If dict-like, the key providing an array-like 
        to plot must be given to var keyword argument.

    Other Parameters
    ----------------
    var    : string
        Name of key in dict-like input that contains data
    time   : array-like or string
        Name of key in dict-like that contains time, or arraylike of datetimes
    levels : array-like, up to 5 levels
        Breaks between levels in data that should be shown as distinct colors
    target : figure or axes
        Target axes or figure window
    colors : array-like
        Colors to use for the color sequence (if insufficient colors, will use as a cycle)
    **kwargs : other keywords
        Other keywords to pass to spacepy.toolbox.binHisto

    Returns
    -------
    binned : tuple
        Tuple of the binned data and bins

    Examples
    --------
    >>> import spacepy.plot as splot
    >>> import spacepy.time as spt
    >>> import spacepy.omni as om
    >>> tt = spt.tickrange('2012/09/28','2012/10/2', 3/24.)
    >>> omni = om.get_omni(tt)
    >>> splot.levelPlot(omni, var='Kp', time='UTC', colors=['seagreen', 'orange', 'crimson'])
    """
    #assume dict-like/key-access, before moving to array-like
    if var is not None:
        try:
            usearr = data[var]
        except KeyError:
            raise KeyError('Key "{1}" not present in data'.format(var))
    else:
        #var is None, so make sure we don't have a dict-like
        if not isinstance(data, Mapping):
            usearr = np.asarray(data)
        else:
            raise TypeError('Data appears to be dict-like without a key being given')
    tflag = False
    if time is not None:
        from scipy.stats import mode
        try:
            times = data[time]
        except (KeyError, ValueError, IndexError):
            times = time
        try:
            times = matplotlib.dates.date2num(times)
            tflag = True
        except AttributeError:
            #the x-data are a non-datetime
            times = np.asarray(time)
        #now add the end-point
        stepsize, dum = mode(np.diff(times), axis=None)
        times = np.hstack([times, times[-1]+stepsize])
    else:
        times = np.asarray(range(0, len(usearr)+1))
    if not colors:
        if len(levels)<=3:
            #traffic light colours that are distinct to protanopes and deuteranopes
            colors = ['lime', 'yellow', 'crimson', 'saddlebrown']
        else:
            colors = matplotlib.rcParams['axes.color_cycle']
    else:
        try:
            assert len(colors) > len(levels)
        except AssertionError:
            #cycle the given colors, if not enough are given
            colors = list(colors)*int(1+len(levels)/len(colors))
    if 'alpha' not in kwargs:
        kwargs['alpha']=0.75
    if 'legend' not in kwargs:
        legend = False
    else:
        legend = kwargs['legend']
        del kwargs['legend']
    fig, ax = set_target(target)
    subset = np.asarray(dmcopy(usearr))

    def fill_between_steps(ax, x, y1, **kwargs):
        y2 = np.zeros_like(y1)
        stepsxx = x.repeat(2)[1:-1]
        stepsyy = y1.repeat(2)
        y2 = np.zeros_like(stepsyy)
        ax.fill_between(stepsxx, stepsyy, y2, **kwargs)
        if mpl.__version__<'1.5.0':
            #pre-v1.5.0, need to manually add an artist for the legend
            p = plt.Rectangle((0, 0), 0, 0, **kwargs)
            ax.add_patch(p)
    
    #below threshold 1
    idx = 0
    inds = usearr>levels[0]
    subset[inds] = np.nan
    kwargs['label'] = u'≤{0}'.format(levels[idx])
    fill_between_steps(ax, times, subset, color=colors[0], zorder=30, **kwargs)
    #for each of the "between" thresholds
    for idx in range(1,len(levels)):
        subset = np.asarray(dmcopy(usearr))
        inds = np.bitwise_or(usearr<=levels[idx-1], usearr>levels[idx])
        subset[inds] = np.nan
        kwargs['label'] = u'>{0},≤{1}'.format(levels[idx-1], levels[idx])
        fill_between_steps(ax, times, subset, color=colors[idx], zorder=30-(idx*2), **kwargs)
    #last
    idx += 1
    try:
        inds = usearr<=levels[idx-1]
        subset = np.asarray(dmcopy(usearr))
        subset[inds] = np.nan
        kwargs['label'] = '>{0}'.format(levels[-1])
        fill_between_steps(ax, times, subset, color=colors[idx], zorder=30-(idx*2), **kwargs)
    except:
        pass

    #if required, set x axis to times
    if tflag:
        try:
            applySmartTimeTicks(ax, data[time])
        except (IndexError, KeyError):
            #using data array to index, so should just use time
            applySmartTimeTicks(ax, time)
        ax.grid(False, which='minor') #minor grid usually looks bad on these...

    if legend:
        ncols = len(levels)+1
        if ncols > 3: ncols = ncols//2
        ax.legend(loc='upper left', ncol=ncols)

    return ax


class spectrogram(Spectrogram):
    """Deprecated variant of :class:`~spacepy.plot.Spectrogram`

    .. deprecated: 0.2.2
       Use :class:`~spacepy.plot.Spectrogram` instead.
    """

    def __init__(self, *args, **kwargs):
        warnings.warn('Use spacepy.plot.Spectrogram (capitalized).',
                      DeprecationWarning)
        super(spectrogram, self).__init__(*args, **kwargs)
