from __future__ import print_function

import numpy as np
from numpy import ma

from matplotlib.cbook import dedent
from matplotlib.ticker import (NullFormatter, ScalarFormatter,
                               LogFormatterMathtext)
from matplotlib.ticker import (NullLocator, LogLocator, AutoLocator,
                               SymmetricalLogLocator)
from matplotlib.transforms import Transform, IdentityTransform
from matplotlib import docstring


class ScaleBase(object):
    """
    The base class for all scales.

    Scales are separable transformations, working on a single dimension.

    Any subclasses will want to override:

      - :attr:`name`
      - :meth:`get_transform`

    And optionally:
      - :meth:`set_default_locators_and_formatters`
      - :meth:`limit_range_for_scale`
    """
    def get_transform(self):
        """
        Return the :class:`~matplotlib.transforms.Transform` object
        associated with this scale.
        """
        raise NotImplementedError()

    def set_default_locators_and_formatters(self, axis):
        """
        Set the :class:`~matplotlib.ticker.Locator` and
        :class:`~matplotlib.ticker.Formatter` objects on the given
        axis to match this scale.
        """
        raise NotImplementedError()

    def limit_range_for_scale(self, vmin, vmax, minpos):
        """
        Returns the range *vmin*, *vmax*, possibly limited to the
        domain supported by this scale.

        *minpos* should be the minimum positive value in the data.
         This is used by log scales to determine a minimum value.
        """
        return vmin, vmax


class LinearScale(ScaleBase):
    """
    The default linear scale.
    """

    name = 'linear'

    def __init__(self, axis, **kwargs):
        pass

    def set_default_locators_and_formatters(self, axis):
        """
        Set the locators and formatters to reasonable defaults for
        linear scaling.
        """
        axis.set_major_locator(AutoLocator())
        axis.set_major_formatter(ScalarFormatter())
        axis.set_minor_locator(NullLocator())
        axis.set_minor_formatter(NullFormatter())

    def get_transform(self):
        """
        The transform for linear scaling is just the
        :class:`~matplotlib.transforms.IdentityTransform`.
        """
        return IdentityTransform()


def _mask_non_positives(a):
    """
    Return a Numpy masked array where all non-positive values are
    masked.  If there are no non-positive values, the original array
    is returned.
    """
    mask = a <= 0.0
    if mask.any():
        return ma.MaskedArray(a, mask=mask)
    return a


def _clip_non_positives(a):
    a[a <= 0.0] = 1e-300
    return a


class LogTransformBase(Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True
    has_inverse = True

    def __init__(self, nonpos):
        Transform.__init__(self)
        if nonpos == 'mask':
            self._handle_nonpos = _mask_non_positives
        else:
            self._handle_nonpos = _clip_non_positives


class Log10Transform(LogTransformBase):
    base = 10.0

    def transform_non_affine(self, a):
        a = self._handle_nonpos(a * 10.0)
        if isinstance(a, ma.MaskedArray):
            return ma.log10(a)
        return np.log10(a)

    def inverted(self):
        return InvertedLog10Transform()


class InvertedLog10Transform(Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True
    has_inverse = True
    base = 10.0

    def transform_non_affine(self, a):
        return ma.power(10.0, a) / 10.0

    def inverted(self):
        return Log10Transform()


class Log2Transform(LogTransformBase):
    base = 2.0

    def transform_non_affine(self, a):
        a = self._handle_nonpos(a * 2.0)
        if isinstance(a, ma.MaskedArray):
            return ma.log(a) / np.log(2)
        return np.log2(a)

    def inverted(self):
        return InvertedLog2Transform()


class InvertedLog2Transform(Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True
    has_inverse = True
    base = 2.0

    def transform_non_affine(self, a):
        return ma.power(2.0, a) / 2.0

    def inverted(self):
        return Log2Transform()


class NaturalLogTransform(LogTransformBase):
    base = np.e

    def transform_non_affine(self, a):
        a = self._handle_nonpos(a * np.e)
        if isinstance(a, ma.MaskedArray):
            return ma.log(a)
        return np.log(a)

    def inverted(self):
        return InvertedNaturalLogTransform()


class InvertedNaturalLogTransform(Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True
    has_inverse = True
    base = np.e

    def transform_non_affine(self, a):
        return ma.power(np.e, a) / np.e

    def inverted(self):
        return NaturalLogTransform()


class LogTransform(Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True
    has_inverse = True

    def __init__(self, base, nonpos):
        Transform.__init__(self)
        self.base = base
        if nonpos == 'mask':
            self._handle_nonpos = _mask_non_positives
        else:
            self._handle_nonpos = _clip_non_positives

    def transform_non_affine(self, a):
        a = self._handle_nonpos(a * self.base)
        if isinstance(a, ma.MaskedArray):
            return ma.log(a) / np.log(self.base)
        return np.log(a) / np.log(self.base)

    def inverted(self):
        return InvertedLogTransform(self.base)


class InvertedLogTransform(Transform):
    input_dims = 1
    output_dims = 1
    is_separable = True
    has_inverse = True

    def __init__(self, base):
        Transf
orm.__init__(self)
        self.base = base

    def transform_non_affine(self, a):
        return ma.power(self.base, a) / self.base

  
  def inverted(self):
        return LogTransform(self.base)


class LogScale(ScaleBase):
    """
    A standard logarithmic scale.  Care is taken so non-positive
    values are not plotted.

    For computational efficiency (to push as much as possible to Numpy
    C code in the common cases), this scale provides different
    transforms depending on the base of the logarithm:

       - base 10 (:class:`Log10Transform`)
       - base 2 (:class:`Log2Transform`)
       - base e (:class:`NaturalLogTransform`)
       - arbitrary base (:class:`LogTransform`)
    """
    name = 'log'

    # compatibility shim
    LogTransformBase = LogTransformBase
    Log10Transform = Log10Transform
    InvertedLog10Transform = InvertedLog10Transform
    Log2Transform = Log2Transform
    InvertedLog2Transform = InvertedLog2Transform
    NaturalLogTransform = NaturalLogTransform
    InvertedNaturalLogTransform = InvertedNaturalLogTransform
    LogTransform = LogTransform
    InvertedLogTransform = InvertedLogTransform

    def __init__(self, axis, **kwargs):
        """
        *basex*/*basey*:
           The base of the logarithm

        *nonposx*/*nonposy*: ['mask' | 'clip' ]
          non-positive values in *x* or *y* can be masked as
          invalid, or clipped to a very small positive number

        *subsx*/*subsy*:
           Where to place the subticks between each major tick.
           Should be a sequence of integers.  For example, in a log10
           scale: ``[2, 3, 4, 5, 6, 7, 8, 9]``

           will place 8 logarithmically spaced minor ticks between
           each major tick.
        """
        if axis.axis_name == 'x':
            base = kwargs.pop('basex', 10.0)
            subs = kwargs.pop('subsx', None)
            nonpos = kwargs.pop('nonposx', 'mask')
        else:
            base = kwargs.pop('basey', 10.0)
            subs = kwargs.pop('subsy', None)
            nonpos = kwargs.pop('nonposy', 'mask')

        if nonpos not in ['mask', 'clip']:
            raise ValueError("nonposx, nonposy kwarg must be 'mask' or 'clip'")

        if base == 10.0:
            self._transform = self.Log10Transform(nonpos)
        elif base == 2.0:
            self._transform = self.Log2Transform(nonpos)
        elif base == np.e:
            self._transform = self.NaturalLogTransform(nonpos)
        else:
            self._transform = self.LogTransfo