# Create once, some test reports
from infra.fixtures.set_audit_user import set_audit_user
from infra.fixtures.dynamic_formula import DynamicFormulaTuple, load_formulas

# List of formulas used by charge schedule
charge_formulas = (

        DynamicFormulaTuple(formula_code='CONTRACT-ENTITIES', formula_description='Build Contract Entity Dict', result_type='dict',
            # model_list="broking.ContractEntry", embedded_formulas=None,
            # Use billing module first for testing
            model_list="billing.BillingEntry", embedded_formulas=None,
            code_snippet="""def compute_formula(model_instances, params, computed_values):
    return {'common.Company': 2, 'common.Branch': 4, 'broking.TradeChannel': 22, 'instrument.InstrumentSubtype': 20,
            'broking.Product' : 3}
""", is_admin_only=False),
        DynamicFormulaTuple(formula_code='CONTRACT-INPUTS', formula_description='Build Contract Input Fields Dict', result_type='dict',
            # model_list="broking.ContractEntry", embedded_formulas=None,
            model_list="billing.BillingEntry", embedded_formulas=None,
            code_snippet="""def compute_formula(model_instances, params, computed_values):
    if isinstance(params, dict):
        return params
    elif model_instances.get('BillingEntry'):
        instance = model_instance.get('BillingEntry')
        # Billing Entry expected (for now)
        return {'QUANTITY': float(instance.billing_quantity),
            'PRICE': float(str(instance.billing_price)),
            'GROSS-PROCEEDS': float(str(instance.billing_quantity * instance.billing_price))}
    else:
        return {'QUANTITY': 500.0, 'PRICE': 4.23, 'GROSS-PROCEEDS': 500.0 * 4.23}
""", is_admin_only=False),

        )

load_formulas(charge_formulas)


from .core import *
from .utils import *
from .vparsers import *

class CentaurusParser(ConditionalWebpageParser):
    middlewares = [ JSONMiddleware() ]
    bs4_parser = "lxml"

    url = "https://centaurus.pl/wp-admin/admin-ajax.php"
    method = "POST"
    data = {
        "order": "ASC",
        "meta_key": "centaurus_kolejnosc",
        "floorRangeMin": "2",
        "floorRangeMax": "18",
        "roomRangeMin": "1",
        "roomRangeMax": "5",
        "flatRangeMin": "39",
        "flatRangeMax": "139",
        "startCount": "15",
        "stepCount": "15",
        "flatscope": "1654,1656,1660,1738,2676,1751,1747,1746,1750,1729,2688,1744,1742,1743,1740",
        "sortOnly": "0",
        "starcaises": "A,B,C",
        "action": "reloadFlatList",
        "uncacher": "7486"
    }

    schema = [
        DataUnit(label="Numer", parser=DOMTextExtractor(), id="number"),
        DataUnit(label="Piętro", parser=IntParser(DOMTextExtractor()), id="floor"),
        DataUnit(label="Pow.", parser=AreaParser(DOMTextExtractor()), id="area"),
        DataUnit(label="Pokoje", parser=IntParser(DOMTextExtractor()), id="rooms"),
        DataUnit(label="Typ", parser=DOMTextExtractor(), id="_type"),
        DataUnit(label="Complete", parser=DOMTextExtractor(), id="_complete"),
        DataUnit(label="Extra pow.", parser=DOMTextExtractor(), id="_extra_room"),
        DataUnit(label="Ekspozycja", parser=DOMTextExtractor(), id="location"),
        DataUnit(label="Cena", parser=NoneParser(), id="none"),
        DataUnit(label="Plan", parser=LinkParser(DOMElementExtractor("a")), id="plan")
    ]

    def get_request_data(self):
        data = super().get_request_data()
        self._start_count = getattr(self, "_start_count", -15) + 15
        data["startCount"] = self._start_count
        return data

    @indexerror_wrapper(return_value=[])
    def find_records(self, data):
        return data[0]

    @indexerror_wrapper(return_value=True)
    def is_last_page(self, data):
        return data[3] < 15

    @tryexcept_wrapper((AttributeError, TypeError), return_value=[])
    def split_record(self, content):
        soup = BeautifulSoup(content, self.bs4_parser)
        records = soup.find_all("div", {"class": "column"})
        for record in records:
            self.remove_span(record)
        return records

    @attributeerror_wrapper(return_value=None)
    def remove_span(self, record):
        record.find("span").extract()

    def modify_record(self, record, soup=None):
        record["fid"] = record["number"]
        return record


"""
Provides Graphics base classes that can be extended upon
"""

#TODO: Stop fighting using matplotlib & just supply the ability to also do other things
#      By now it's a dependency so stop worrying and love the bomb

# import matplotlib.figure
# import matplotlib.axes
import os, weakref, numpy as np, functools

from .Properties import GraphicsPropertyManager, GraphicsPropertyManager3D
from .Styling import Styled, ThemeManager, PlotLegend
from .Backends import Backends

__all__ = ["GraphicsBase", "Graphics", "Graphics3D", "GraphicsGrid"]
__reload_hook__ = ['.Properties', ".Styling", ".Backends"]

class GraphicsException(Exception):
    pass

########################################################################################################################
#
#                                               GraphicsBase
#
from abc import *

class GraphicsBase(metaclass=ABCMeta):
    """
    The base class for all things Graphics
    Defines the common parts of the interface with some calling into matplotlib
    """
    _figure_mapping = weakref.WeakValueDictionary()
    _figure_children = weakref.WeakKeyDictionary()
    @classmethod
    def resolve_figure_graphics(cls, fig):
        if fig in cls._figure_mapping:
            return cls._figure_mapping[fig]
    @classmethod
    def add_figure_graphics(cls, fig, graphics):
        if fig in cls._figure_mapping:
            parent = cls._figure_mapping[fig]
            # cls._figure_children[parent].add(graphics)
            cls._figure_children[parent].append(graphics)
        else:
            cls._figure_mapping[fig] = graphics
            cls._figure_children[graphics] = []
    @classmethod
    def remove_figure_mapping(cls, fig):
        if fig in cls._figure_mapping:
            parent = cls._figure_mapping[fig]
            del cls._figure_mapping[fig]
            del cls._figure_children[parent]
    @classmethod
    def get_child_graphics(cls, fig):
        if fig in cls._figure_mapping:
            parent = cls._figure_mapping[fig]
            return cls._figure_children[parent]

    _axes_mapping = weakref.WeakValueDictionary()
    _axes_children = weakref.WeakKeyDictionary()
    @classmethod
    def resolve_axes_graphics(cls, axes):
        if hasattr(axes, 'figure'): # ignore GraphicsGrids
            if axes in cls._axes_mapping:
                return cls._axes_mapping[axes]
    @classmethod
    def add_axes_graphics(cls, axes, graphics):
        if hasattr(axes, 'figure'):
            if axes in cls._axes_mapping:
                parent = cls._axes_mapping[axes]
                cls._axes_children[parent].append(graphics)
            else:
                cls._axes_mapping[axes] = graphics
                cls._axes_children[graphics] = []
    @classmethod
    def remove_axes_mapping(cls, axes):
        if hasattr(axes, 'figure'):
            if axes in cls._axes_mapping:
                parent = cls._axes_mapping[axes]
                del cls._axes_mapping[axes]
                del cls._axes_children[parent]
    @classmethod
    def get_axes_child_graphics(cls, axes):
        if hasattr(axes, 'figure'):
            if axes in cls._axes_mapping:
                parent = cls._axes_mapping[axes]
                return cls._axes_children[parent]

    opt_keys = {
        'background',
        'axes_labels',
        'plot_label',
        'plot_range',
        'plot_legend',
        'legend_style',
        'ticks',
        'ticks_style',
        'ticks_label_style',
        'axes_bbox',
        'scale',
        'padding',
        'spacings',
        'aspect_ratio',
        'image_size',
        'event_handlers',
        'animated',
        'epilog'
        'prolog',
        'subplot_kw',
        'theme',
        'annotations'
    }

    @staticmethod
    def _split_props_list(props, filter_set):
        excl = {k: props[k] for k in props.keys() - filter_set}
        incl = {k: props[k] for k in props.keys() & filter_set}
        return incl, excl

    def get_raw_attr(self, key):
        exc = None
        try:
            v = object.__getattribute__(self, '_' + key)  # we overloaded getattr
        except AttributeError as e:
            try:
                v = object.__getattribute__(self._prop_manager, '_' + key)  # we overloaded getattr
            except AttributeError as e:
                exc = e
            else:
                exc = None
        if exc is not None:
            raise exc # from None
        return v

    def _get_def_opt(self, key, val, theme, parent=None):
        if val is None:
            try:
                v = object.__getattribute__(self, '_'+key) # we overloaded getattr
            except AttributeError as e:
                try:
                    v = object.__getattribute__(self._prop_manager, '_' + key)  # we overloaded getattr
                except AttributeError:
                    if parent is not None:
                        try:
                            v = parent.get_raw_attr(key)
                        except AttributeError:
                            v = None
                    else:
                        v = None
            if v is None and key in self.default_style:
                v = self.default_style[key]
            if v is None and theme is not None and key in theme:
                v = theme[key]
            return v
        else:
            return val

    def _update_copy_opt(self, key, val):
        if not self._in_init:
            has_val = False
            if key not in self._init_opts or self._init_opts[key] is None:
                has_val = True
                old = self._get_def_opt(key, None, self.theme)
            elif key in self._init_opts:
                has_val = True
                old = self._init_opts[key]
            else:
                old = None
            if has_val:
                changed = val != old
                if isinstance(changed, np.ndarray):
                    changed = changed.any()
                if changed:
                    self._init_opts[key] = val

    layout_keys = {
        'name',
        'figure',
        'tighten',
        'axes',
        'subplot_kw',
        'parent',
        'image_size',
        'figsize',
        'padding',
        'aspect_ratio',
        'interactive',
        'reshowable',
        'mpl_backend',
        'theme',
        'prop_manager',
        'theme_manager',
        'managed',
        # 'inset',
        'event_handlers',
        'animated',
        'prolog',
        'epilog',
        'annotations'
    }
    def __init__(self,
                 *args,
                 name=None,
                 figure=None,
                 tighten=False,
                 axes=None,
                 subplot_kw=None,
                 parent=None,
                 image_size=None,
                 padding=None,
                 aspect_ratio=None,
                 interactive=None,
                 reshowable=None,
                 mpl_backend=None,
                 theme=None,
                 prop_manager=GraphicsPropertyManager,
                 theme_manager=ThemeManager,
                 managed=None,
                 # inset=False,
                 strict=True,
                 # annotations=None,
                 **opts
                 ):
        """
        :param args:
        :type args:
        :param figure:
        :type figure: matplotlib.figure.Figure | None
        :param axes:
        :type axes: matplotlib.axes.Axes | None
        :param subplot_kw:
        :type subplot_kw: dict | None
        :param parent:
        :type parent: GraphicsBase | None
        :param opts:
        :type opts:
        """

        self.name = name

        self._init_opts = dict(opts, # for copying
                               tighten=tighten,
                               axes=axes,
                               subplot_kw=subplot_kw,
                               image_size=image_size,
                               padding=padding,
                               aspect_ratio=aspect_ratio,
                               interactive=interactive,
                               reshowable=reshowable,
                               mpl_backend=mpl_backend,
                               theme=theme,
                               prop_manager=prop_manager,
                               theme_manager=theme_manager,
                               managed=managed
                               )
        self._in_init = True

        # we try to only load pyplot once and use it
        # as an API rather than a global import
        # this is to potentially support non-MPL interfaces
        # in the future
        # self.pyplot = None
        self._mpl_loaded = False

        if subplot_kw is None:
            subplot_kw = {}

        self.interactive = interactive
        self.mpl_backend = mpl_backend
        if isinstance(figure, GraphicsBase):
            parent = figure
        inherit_layout =parent is not None and (axes is None or parent.axes is axes) # check inset
        # # TODO: generalize this to a set of inherited props...
        # if image_size is None: # gotta copy in some layout stuff..
        #     image_size = parent.image_size
        # else:
        #     parent.image_size = image_size
        # if aspect_ratio is None:
        #     aspect_ratio = parent.aspect_ratio
        # else:
        #     parent.aspect_ratio = aspect_ratio
        # if interactive is None:
        #     interactive = parent.interactive
        # else:
        #     parent.interactive = interactive
        if inherit_layout:
            prop_manager = parent._prop_manager

        theme = self._get_def_opt('theme', theme, {})
        self.theme=theme
        self.theme_manager=theme_manager
        # if theme is not None:
        #     theme_dict = theme_manager.resolve_theme(theme)[1]
        # else:
        #     theme_dict = {}

        if managed is None:
            if figure is not None and isinstance(figure, GraphicsBase):
                managed = figure.managed
            else:
                managed = False
        self.managed = managed
        self._inset = None

        interactive = self._get_def_opt('interactive', interactive, theme)
        self.interactive = interactive

        reshowable = self._get_def_opt('reshowable', reshowable, theme)
        self.reshowable = reshowable

        theme_parent = parent if inherit_layout else None
        aspect_ratio = self._get_def_opt('aspect_ratio', aspect_ratio,  theme, theme_parent)
        image_size = self._get_def_opt('image_size', image_size, theme, theme_parent)
        padding = self._get_def_opt('padding', padding, theme, theme_parent)
        if figure is None and image_size is not None and 'figsize' not in subplot_kw:
            try:
                w, h = image_size
            except TypeError:
                w = image_size
                asp = aspect_ratio
                if asp is None or isinstance(asp, str):
                    asp = 4.8/6.4
                h = w * asp
            if padding is not None:
                pw, ph = padding
                try:
                    pwx, pwy = pw
                except (TypeError, ValueError):
                    pwx = pwy = pw
                try:
                    phx, phy = ph
                except (TypeError, ValueError):
                    phx = phy = ph
                w += pwx + pwy
                h += phx + phy
                image_size = (w, h)
            subplot_kw['figsize'] = (w/72., h/72.)

        self.subplot_kw = subplot_kw
        with self.theme_manager.from_spec(self.theme):
            self.figure, self.axes = self._init_suplots(figure, axes, *args, **subplot_kw)
        self.figure._called_show = False # for avoiding excess show calls with a custom backend
        if self.inset:
            self.add_axes_graphics(self.axes, self)
        else:
            self.add_figure_graphics(self.figure, self)

        if not self.interactive:
            self.pyplot.mpl_disconnect()

        if isinstance(prop_manager, type):
            prop_manager = prop_manager(self, self.figure, self.axes, managed=managed)
        self._prop_manager = prop_manager
        self._colorbar_axis = None
        self.set_options(padding=padding, aspect_ratio=aspect_ratio, image_size=image_size, strict=strict, **opts)

        self.event_handler = None
        self._shown = False
        self.animator = None
        self.tighten = tighten

        self._in_init = False

    class MPLManager:
        _plt = None
        _mpl = None
        def __init__(self, figure):
            self.fig = figure
            self.fig.mpl = self
            self.jlab = None
            self._old_interactive = None
            self._drawer = None
            self._draw_all = None
            self._old_show = None
            self._old_mpl_backend = None
            self._old_magic_backend = None
        @property
        def plt(self):
            if self._plt is None:
                import matplotlib.pyplot as plt
                type(self)._plt = plt
            return self._plt
        @property
        def mpl(self):
            if self._mpl is None:
                import matplotlib as mpl
                type(self)._mpl = mpl
            return self._mpl
        def draw_if_interactive(self, *args, **kwargs):
            pass
        def magic_backend(self, backend):
            try:
                from IPython.core.getipython import get_ipython
            except ImportError:
                pass
            else:
                shell = get_ipython()
                ip_name = type(shell).__name__
                in_nb = ip_name == 'ZMQInteractiveShell'
                if in_nb:
                    try:
                        from IPython.core.magics import PylabMagics
                    except ImportError:
                        pass
                    else:
                        set_jupyter_backend = PylabMagics(shell).matplotlib
                        set_jupyter_backend(backend)

        def __enter__(self):
            if self.fig.mpl_backend is not None:
                self._old_mpl_backend = self.mpl.get_backend()
                self.mpl.use(self.fig.mpl_backend)
            self._was_interactive = self.plt.isinteractive()
            if not self.fig.managed:
                # import matplotlib.pyplot as plt
                # plt.ioff
                # if 'inline' in self.mpl.get_backend():
                #     backend = self.plt._backend_mod
                #     self.plt.show = ...
                #     self._old_show = backend.show
                #     backend.show = self.jupyter_show
                if not self.fig.interactive:
                    self.plt.ioff()
                    # manager.canvas.mpl_disconnect(manager._cidgcf)
                    # self._drawer = self.plt.draw_if_interactive
                    # self._draw_all = self.plt.draw_all
                    # self.plt.draw_if_interactive = self.draw_if_interactive
                    # self.plt.draw_all = self.draw_if_interactive
                    # if self.fig.mpl_backend is None:
                    #     self._old_magic_backend = self.mpl.get_backend()
                    #     self.magic_backend('Agg')
                else:
                    self.plt.ion()
                    # if self.fig.mpl_backend is None:
                    #     self._old_magic_backend = self.mpl.get_backend()
                    #     if 'inline' not in self._old_magic_backend:
                    #         self.magic_backend('inline')
            self.fig._mpl_loaded=True
            return self.plt
        def mpl_disconnect(self):
            # this is a hack that might need to be updated in the future
            if 'inline' in self.mpl.get_backend():
                try:
                    from matplotlib._pylab_helpers import Gcf
                    canvas = self.fig.figure.canvas
                    num = canvas.manager.num
                    if all(hasattr(num, attr) for attr in ["num", "_cidgcf", "destroy"]):
                        manager = num
                        if Gcf.figs.get(manager.num) is manager:
                            Gcf.figs.pop(manager.num)
                        else:
                            return
                    else:
                        try:
                            manager = Gcf.figs.pop(num)
                        except KeyError:
                            return
                    # manager.canvas.mpl_disconnect(manager._cidgcf)
                    # self.fig.figure.canvas.mpl_disconnect(
                    #     self.fig.figure.canvas.manager._cidgcf
                    # )
                except:
                    pass

        def mpl_connect(self):
            if 'inline' in self.mpl.get_backend():
                # try:
                from matplotlib._pylab_helpers import Gcf
                canvas = self.fig.figure.canvas
                manager = canvas.manager
                num = canvas.manager.num
                Gcf.figs[num] = manager
                manager._cidgcf = canvas.mpl_connect(
                    "button_press_event", lambda event: Gcf.set_active(manager)
                )
                # manager.canvas.mpl_disconnect(manager._cidgcf)
                # self.fig.figure.canvas.mpl_disconnect(
                #     self.fig.figure.canvas.manager._cidgcf
                # )
                # except:
                #     pass

        def jupyter_show(self, close=None, block=None):
            """Show all figures as SVG/PNG payloads sent to the IPython clients.
            Parameters
            ----------
            close : bool, optional
                If true, a ``plt.close('all')`` call is automatically issued after
                sending all the figures. If this is set, the figures will entirely
                removed from the internal list of figures.
            block : Not used.
                The `block` parameter is a Matplotlib experimental parameter.
                We accept it in the function signature for compatibility with other
                backends.
            """
            from matplotlib._pylab_helpers import Gcf
            from IPython.core.display import display
            mpl_inline = self.plt._backend_mod

            if close is None:
                close = mpl_inline.InlineBackend.instance().close_figures
            try:
                for figure_manager in [Gcf.get_active()]:
                    display(
                        figure_manager.canvas.figure,
                        metadata=mpl_inline._fetch_figure_metadata(figure_manager.canvas.figure)
                    )
            finally:
                self._to_draw = []
                # only call close('all') if any to close
                # close triggers gc.collect, which can be slow
                if close and Gcf.get_all_fig_managers():
                    self.plt.close('all')
        # This flag will be reset by draw_if_interactive when called
        _draw_called = False
        # list of figures to draw when flush_figures is called
        _to_draw = []
        def __exit__(self, exc_type, exc_val, exc_tb):
            if self._old_mpl_backend is not None:
                self.mpl.use(self._old_mpl_backend)
            if self._drawer is not None:
                self.plt.draw_if_interactive = self._drawer
                self._drawer = None
            if self._draw_all is not None:
                self.plt.draw_all = self._drawer
                self._drawer = None
            if self._old_show is not None:
                self.plt._backend_mod.show = self._old_show
                self._old_show = None

            if self._old_magic_backend is not None:
                if 'inline' in self._old_magic_backend:
                    self.magic_backend('inline')
                else:
                    self.mpl.use(self._old_mpl_backend)
            if self._was_interactive and not self.plt.isinteractive():
                self.plt.ion()
    @property
    def pyplot(self):
        return self.MPLManager(self)

    @staticmethod
    def _subplot_init(*args, mpl_backend=None, figure=None, **kw):
        with figure.pyplot as plt:
            return plt.subplots(*args, **kw)

    def _init_suplots(self, figure, axes, *args, **kw):
        """Initializes the subplots for the Graphics object

        :param figure:
        :type figure:
        :param axes:
        :type axes:
        :param args:
        :type args:
        :param kw:
        :type kw:
        :return: figure, axes
        :rtype: matplotlib.figure.Figure, matplotlib.axes.Axes
        """

        if figure is None:
            figure, axes = self._subplot_init(*args, mpl_backend=self.mpl_backend, figure=self, **kw)
        elif isinstance(figure, GraphicsBase) or all(hasattr(figure, h) for h in ['layout_keys']):
            if axes is None:
                axes = figure.axes # type: matplotlib.axes.Axes
            figure = figure.figure # type: matplotlib.figure.Figure

        if axes is None:
            if not hasattr(figure, 'add_subplot'):
                figure = figure.figure
            if not hasattr(figure, 'axes'):
                axes = figure.add_subplot(1, 1, 1) # type: matplotlib.axes.Axes
            else:
                axes = figure.axes

        return figure, axes

    @property
    def parent(self):
        if self.inset:
            return self.resolve_axes_graphics(self.axes)
        else:
            return self.resolve_figure_graphics(self.figure)
    @property
    def figure_parent(self):
        return self.resolve_figure_graphics(self.figure)
    @property
    def inset(self):
        fp = self.figure_parent
        return not self.managed and fp is not None and (self.axes is not fp.axes)
    @property
    def children(self):
        if self.parent is self:
            if self.inset:
                return self.get_axes_child_graphics(self.axes)
            else:
                return self.get_child_graphics(self.figure)
        else:
            return None

    @property
    def event_handlers(self):
        from .Interactive import EventHandler
        h = self.event_handler  # type: EventHandler
        if h is not None:
            h = h.data
        return h

    @property
    def animated(self):
        return self._animated

    def bind_events(self, *handlers, **events):
        from .Interactive import EventHandler

        if len(handlers) > 0 and isinstance(handlers[0], dict):
            handlers = handlers[0]
        elif len(handlers) == 0 or (len(handlers) > 0 and handlers[0] is not None):
            handlers = dict(handlers)
        if isinstance(handlers, dict):
            handlers = dict(handlers, **events)
            if self.event_handler is None:
                self.event_handler = EventHandler(self, **handlers)
            else:
                self.event_handler.bind(**handlers)

    def create_animation(self, *args, **opts):
        from .Interactive import Animator

        if len(args) > 0 and args[0] is not None:
            if self.animator is not None:
                self.animator.stop()
            self.animator = Animator(self, *args, **opts)

    known_keys = layout_keys
    def _check_opts(self, opts):
        diff = opts.keys() - self.known_keys
        if len(diff) > 0:
            raise ValueError("unknown options for {}: {}".format(
                type(self).__name__, list(diff)
            ))
    def set_options(self,
                    event_handlers=None,
                    animated=None,
                    prolog=None,
                    epilog=None,
                    strict=True,
                    **opts
                    ):
        """Sets options for the plot
        :param event_handlers:
        :param animated:
        :param opts:
        :type opts:
        :return:
        :rtype:
        """
        if strict:
            self._check_opts(opts)

        self.bind_events(event_handlers)
        self._animated = animated
        self.create_animation(animated)

        if prolog is not None or not hasattr(self, '_prolog'):
            self._prolog = prolog
            if self._prolog is not None:
                self.prolog = prolog

        if epilog is not None or not hasattr(self, '_epilog'):
            self._epilog = epilog
            if self._epilog is not None:
                self.epilog = epilog

    @property
    def prolog(self):
        return self._prolog
    @prolog.setter
    def prolog(self, p):
        self._update_copy_opt('prolog', p)
        # might want to clear the elements in the prolog?
        self._prolog = p

    @property
    def epilog(self):
        return self._epilog
    @epilog.setter
    def epilog(self, e):
        self._update_copy_opt('epilog', e)
        # might want to clear the elements in the epilog?
        self._epilog = e

    # def __getattr__(self, item):
    #     reraise_error = None
    #     axes = object.__getattribute__(self, 'axes')
    #     try:
    #         meth = getattr(axes, item)
    #     except AttributeError as e:
    #         if 'Axes' not in e.args[0] or item not in e.args[0]:
    #             reraise_error = e
    #         else:
    #             try:
    #                 meth = getattr(self.figure, item)
    #             except AttributeError as e:
    #                 if 'Figure' not in e.args[0] or item not in e.args[0]:
    #                     reraise_error = e
    #                 else:
    #                     reraise_error = AttributeError("'{}' object has no attribute '{}'".format(
    #                         type(self).__name__,
    #                         item
    #                     ))
    #     if reraise_error is not None:
    #         raise reraise_error
    #
    #     return meth

    def copy_axes(self):
        """Copies the axes object

        :return:
        :rtype: matplotlib.axes.Axes
        """
        raise GraphicsException("{}.{} this hack doesn't work anymore".format(
                                type(self).__name__,
                                "copy_axes"
                                ))

        import pickle, io

        buf = io.BytesIO()
        pickle.dump(self.axes, buf)
        buf.seek(0)
        return pickle.load(buf)

    def refresh(self):
        """Refreshes the axes

        :return:
        :rtype:
        """

        self.axes = self.copy_axes()
        self.figure.close()
        self.figure = self.axes.figure

        return self

    @property
    def opts(self):
        opt_dict = {}
        for k in self.opt_keys:
            if (
                    k in self.__dict__
                    or hasattr(self._prop_manager, k)
            ):
                opt_dict[k] = getattr(self, k)
            elif (
                    "_"+k in self.__dict__
                    or hasattr(self, "_"+k)
            ):
                opt_dict[k] = getattr(self, "_" + k)
        return opt_dict

    def copy(self, **kwargs):
        """Creates a copy of the object with new axes and a new figure

        :return:
        :rtype:
        """
        return self.change_figure(None, **kwargs)
    def _get_init_opts(self, parent_opts, unmerged_keys=None):
        if unmerged_keys is None:
            unmerged_keys = self.layout_keys
        base = self._init_opts.copy()
        problems = unmerged_keys & base.keys()
        for k in problems & parent_opts.keys():
            del base[k]
        return base
    def change_figure(self, new, *init_args, figs=None, **init_kwargs):
        """Creates a copy of the object with new axes and a new figure

        :return:
        :rtype:
        """
        parent = self.figure_parent
        figs = {} if figs is None else figs
        if parent is self:
            # opts = self.get_init_opts(**init_kwargs)
            figs[self] = base = self._change_figure(new, *init_args, parent_opts={}, **init_kwargs)
            parent_opts = dict(self._get_init_opts({}), **init_kwargs)
            for c in self.children:
                figs[c] = c._change_figure(base, parent_opts=parent_opts)
        else:
            parent.change_figure(new, *init_args, figs=figs, **init_kwargs)
        return figs[self]
    def _get_init_args(self, *init_args):
        return init_args
    def _change_figure(self, new, *init_args, parent_opts=None, **init_kwargs):
        """Creates a copy of the object with new axes and a new figure

        :return:
        :rtype:
        """
        return type(self)(
            *self._get_init_args(*init_args),
            **dict(
                self._get_init_opts(parent_opts),
                figure=new,
                **init_kwargs
            )
        )
    def prep_show(self):
        self.set_options(**self.opts)  # matplotlib is dumb so it makes sense to just reset these again...
        if self.prolog is not None:
            self._prolog_graphics = [p.plot(self.axes, graphics=self) for p in self.prolog] # not sure this is doing what it should...
        if self.epilog is not None:
            self._epilog_graphics = [e.plot(self.axes, graphics=self) for e in self.epilog]
        if self.tighten:
            self.figure.tight_layout()
        self._shown = True
        return self
    def show(self, reshow=None):
        from .VTKInterface import VTKWindow

        if isinstance(self.figure, VTKWindow):
            self.figure.show()
        else:
            if reshow or not self._shown:
                self.prep_show()
                if not self.managed:
                    ni = not self.interactive
                    try:
                        with self.pyplot as plt:
                            if ni:
                                self.pyplot.mpl_connect()
                            self.figure._called_show = True
                            plt.show()
                    finally:
                        self.figure._called_show = False
                        self.interactive = not ni
                        if ni:
                            self.pyplot.mpl_disconnect()
            else:
                if self.reshowable:
                    self.show(reshow=True)
                else:
                    # self._shown = False
                    self.copy().show()
                    # raise GraphicsException("{}.show can only be called once per object".format(type(self).__name__))

    def close(self, force=False):
        if (
                force
                or self.figure not in self._figure_mapping
                or self.resolve_figure_graphics(self.figure) is self
                or self.resolve_axes_graphics(self.axes) is self
        ): # parent manages cleanup
            if self.inset:
                # print("removing inset axes: {}".format(self))
                self.axes.remove()
            else:
                # print("closing: {}".format(self))
                if self._mpl_loaded:
                    with self.pyplot as plt:
                        return plt.close(self.figure)
                self.remove_figure_mapping(self.figure)
        # else:
        #     print("close failed: {}".format(self))

    def __del__(self):
        try:
            self.close()
        except AttributeError:
            pass

    def __repr__(self):
        return "{}({}, figure={}<{}>)".format(
            type(self).__name__,
            id(self) if self.name is None else self.name,
            self.figure,
            id(self.figure)
        )

    def clear(self):
        ax = self.axes  # type: matplotlib.axes.Axes
        all_things = ax.artists + ax.patches
        for a in all_things:
            a.remove()
        self.remove_figure_mapping(self.figure)

    _display_locks = set()
    def _ipython_display_(self):
        if self not in self._display_locks:  # don't want to call this over and over...
            self._display_locks.add(self)
            try:
                self.show()
            finally:
                self._display_locks.remove(self)
    def _repr_html_(self):
        # hacky, but hopefully enough to make it work?
        return self.figure._repr_html_()

    def savefig(self, where, format=None, **kw):
        """
        Saves the image to file
        :param where:
        :type where:
        :param format:
        :type format:
        :param kw:
        :type kw:
        :return: file it was saved to (I think...?)
        :rtype: str
        """
        if 'facecolor' not in kw:
            kw['facecolor'] = self.background# -_- stupid MPL
        if format is None:
            format = os.path.splitext(where)[1].split('.')[-1]
        self.prep_show()
        return self.figure.savefig(where,
                    format=format,
                    **kw
                    )
    def to_png(self):
        """
        Used by Jupyter and friends to make a version of the image that they can display, hence the extra 'tight_layout' call
        :return:
        :rtype:
        """
        import io
        buf = io.BytesIO()
        self.prep_show()
        self.figure.tight_layout()
        fig = self.figure
        fig.savefig(buf,
                    format='png',
                    facecolor = self.background #-_- stupid MPL
                    )
        buf.seek(0)
        return buf

    def _repr_png_(self):
        # currently assumes a matplotlib backend...
        return self.to_png().read()

    def add_colorbar(self,
                     graphics=None,
                     norm=None,
                     cmap=None,
                     size=(20, 200),
                     tick_padding=40,
                     **kw
                     ):
        fig = self.figure  # type: matplotlib.figure.Figure
        ax = self.axes  # type: matplotlib.axes.Axes
        if graphics is None:
            import matplotlib.cm as cm
            graphics = cm.ScalarMappable(norm=norm, cmap=cmap)

        if 'cax' not in kw:
            if self._colorbar_axis is None:
                # TODO: I'd like to have better control over how much space this colorbar takes in the future
                #   it might be a mistake to make this not percentage based...
                W, H = self.image_size
                if size[0] < 1:
                    size = (W * size[0], H * size[1])
                size = (size[0] + tick_padding, size[1])
                cur_padding = self.padding
                if cur_padding[0][1] < size[0] + 5:
                    self.padding_right = size[0] + 5
                    new_padding = self.padding
                    wpad_old = cur_padding[0][0] + cur_padding[0][1]
                    wpad_new = new_padding[0][0] + new_padding[0][1]
                    wdiff = wpad_new - wpad_old
                else:
                    wdiff = 0
                # we need to now shrink the spacing by enough to compensate for this
                # it would be best to mess with the figure sizes themselves, but this is the easier
                # solution for the moment...
                sp = self.spacings
                if sp is not None:
                    ws, hs = sp
                    nspaces = self.shape[1] - 1
                    ws = ws - (wdiff / nspaces)
                    ws = max(ws, 0)
                    self.spacings = (ws, hs)
                cbw = size[0] / W
                tw = tick_padding / W
                cbh = size[1] / H
                xpos = 1 - cbw
                ypos = .5 - cbh / 2  # new_padding[1][0]/H

                theme = self.theme
                if self.theme is not None:
                    with self.theme_manager.from_spec(theme):
                        self._colorbar_axis = fig.add_axes([xpos, ypos, cbw - tw, cbh])
                else:
                    self._colorbar_axis = fig.add_axes([xpos, ypos, cbw - tw, cbh])
            kw['cax'] = self._colorbar_axis

        return fig.colorbar(graphics, **kw)

    axes_params = {"adjustable", "agg_filter", "alpha", "anchor", "animated", "aspect",
                   "autoscale_on", "autoscalex_on", "autoscaley_on", "axes_locator", "axisbelow",
                   "box_aspect", "clip_box", "clip_on", "clip_path", "facecolor",
                   "frame_on", "gid", "in_layout", "label", "navigate",
                   "navigate_mode", "path_effects", "picker", "position",
                   "prop_cycle", "rasterization_zorder", "rasterized", "sketch_params",
                   "snap", "title", "transform", "url", "visible", "xbound",
                   "xlabel", "xlim", "xmargin", "xscale", "xticklabels", "xticks",
                   "ybound", "ylabel", "ylim", "ymargin", "yscale", "yticklabels",
                   "yticks", "zorder"} # not sure what to do with these yet...
    inset_options = dict()
    axes_keys = set()
    _axes_padding_offset = [1, 0]
    def create_inset(self, bbox, coordinates='scaled', graphics_class=None, **opts):
        if hasattr(bbox, 'get_points'):
            bbox = bbox.get_points()
        ((lx, by), (rx, ty)) = bbox

        if coordinates == 'absolute':
            raise NotImplementedError("can't construct inset axes with absolute coordinates")
        elif coordinates == 'scaled': # scaled to live within the frame
            ((alx, aby), (arx, aty)) = self.axes.bbox.get_points()
            alx = alx + self._axes_padding_offset[0]
            arx = arx + self._axes_padding_offset[0]
            aby = aby + self._axes_padding_offset[1]
            aty = aty + self._axes_padding_offset[1]
            w, h = self.figure_parent.image_size
            # we scale the coordinates to live within the axes screenbox
            sl = alx / w
            sr = arx / w
            sw = (sr - sl)
            sb = aby / h
            st = aty / h
            sh = (st - sb)
            lx = lx * sw + sl
            rx = rx * sw + sl
            by = by * sh + sb
            ty = ty * sh + sb

        if graphics_class is None:
            graphics_class = type(self)
        opts = dict(self.inset_options, **opts)
        # raise Exception(opts, self.axes_keys)
        # ax_par, fig_par = self._split_props_list(opts, self.axes_keys)

        with self.theme_manager.from_spec(self.theme):
            ax = self.figure.add_axes([lx, by, rx-lx, ty-by])
        return graphics_class(figure=self, axes=ax, **opts)

########################################################################################################################
#
#                                               Graphics
#
class Graphics(GraphicsBase):
    """
    A mini wrapper to matplotlib.pyplot to create a unified interface I know how to work with
    """
    default_style = dict(
        theme='mccoy',
        frame=((True, False), (True, False)),
        image_size=(370, 345),
        padding=((60, 10), (35, 10)),
        interactive=False,
        reshowable=False,
        aspect_ratio='auto'
    )

    axes_keys = {
        'plot_label',
        'plot_legend',
        'legend_style'
        'axes_labels',
        'frame',
        'frame_style',
        'plot_range',
        'ticks',
        'ticks_style',
        'ticks_label_style',
        'axes_bbox'
    }
    figure_keys = {
        'scale',
        'aspect_ratio'
        'image_size',
        'padding',
        'spacings',
        'background',
        'colorbar'
    }
    layout_keys = axes_keys | figure_keys | GraphicsBase.layout_keys
    known_keys = layout_keys
    def set_options(self,
                    axes_labels=None,
                    plot_label=None,
                    plot_range=None,
                    plot_legend=None,
                    legend_style=None,
                    frame=None,
                    frame_style=None,
                    ticks=None,
                    scale=None,
                    padding=None,
                    spacings=None,
                    ticks_style=None,
                    ticks_label_style=None,
                    image_size=None,
                    axes_bbox=None,
                    aspect_ratio=None,
                    background=None,
                    colorbar=None,
                    prolog=None,
                    epilog=None,

                    **parent_opts
                    ):

        super().set_options(prolog=prolog, epilog=epilog, **parent_opts)

        opts = (
            ('plot_label', plot_label),
            ('plot_legend', plot_legend),
            ('legend_style', legend_style),
            ('axes_labels', axes_labels),
            ('frame', frame),
            ('frame_style', frame_style),
            ('plot_range', plot_range),
            ('ticks', ticks),
            ('ticks_style', ticks_style),
            ('ticks_label_style', ticks_label_style),
            ('scale', scale),
            ('aspect_ratio', aspect_ratio),
            ('image_size', image_size),
            ('axes_bbox', axes_bbox),
            ('padding', padding),
            ('spacings', spacings),
            ('background', background),
            ('colorbar', colorbar)
        )
        for oname, oval in opts:
            oval = self._get_def_opt(oname, oval, {})
            if oval is not None:
                setattr(self, oname, oval)

    @property
    def artists(self):
        return []

    # attaching custom property setters
    @property
    def plot_label(self):
        return self._prop_manager.plot_label
    @plot_label.setter
    def plot_label(self, value):
        self._update_copy_opt('plot_label', value)
        self._prop_manager.plot_label = value

    @property
    def plot_legend(self):
        return self._prop_manager.plot_legend
    @plot_legend.setter
    def plot_legend(self, value):
        self._update_copy_opt('plot_legend', value)
        self._prop_manager.plot_legend = value

    @property
    def legend_style(self):
        return self._prop_manager.legend_style
    @legend_style.setter
    def legend_style(self, value):
        self._update_copy_opt('legend_style', value)
        self._prop_manager.legend_style = value

    @property
    def axes_labels(self):
        return self._prop_manager.axes_labels
    @axes_labels.setter
    def axes_labels(self, value):
        self._update_copy_opt('axes_labels', value)
        self._prop_manager.axes_labels = value

    @property
    def frame(self):
        return self._prop_manager.frame
    @frame.setter
    def frame(self, value):
        self._update_copy_opt('frame', value)
        self._prop_manager.frame = value

    @property
    def frame_style(self):
        return self._prop_manager.frame_style
    @frame_style.setter
    def frame_style(self, value):
        self._update_copy_opt('frame_style', value)
        self._prop_manager.frame_style = value

    @property
    def plot_range(self):
        return self._prop_manager.plot_range
    @plot_range.setter
    def plot_range(self, value):
        self._update_copy_opt('plot_range', value)
        self._prop_manager.plot_range = value

    @property
    def ticks(self):
        return self._prop_manager.ticks
    @ticks.setter
    def ticks(self, value):
        self._update_copy_opt('ticks', value)
        self._prop_manager.ticks = value

    @property
    def ticks_style(self):
        return self._prop_manager.ticks_style
    @ticks_style.setter
    def ticks_style(self, value):
        self._update_copy_opt('ticks_style', value)
        self._prop_manager.ticks_style = value

    @property
    def ticks_label_style(self):
        return self._prop_manager.ticks_label_style
    @ticks_label_style.setter
    def ticks_label_style(self, value):
        self._update_copy_opt('ticks_label_style', value)
        self._prop_manager.ticks_label_style = value

    @property
    def scale(self):
        return self._prop_manager.scale
    @scale.setter
    def scale(self, value):
        self._update_copy_opt('scale', value)
        self._prop_manager.scale = value

    @property
    def axes_bbox(self):
        return self._prop_manager.axes_bbox
    @axes_bbox.setter
    def axes_bbox(self, value):
        self._update_copy_opt('axes_bbox', value)
        self._prop_manager.axes_bbox = value

    @property
    def aspect_ratio(self):
        return self._prop_manager.aspect_ratio
    @aspect_ratio.setter
    def aspect_ratio(self, value):
        self._update_copy_opt('aspect_ratio', value)
        self._prop_manager.aspect_ratio = value

    @property
    def image_size(self):
        return self._prop_manager.image_size
    @image_size.setter
    def image_size(self, value):
        self._update_copy_opt('image_size', value)
        self._prop_manager.image_size = value

    @property
    def padding(self):
        return self._prop_manager.padding
    @padding.setter
    def padding(self, value):
        self._update_copy_opt('padding', value)
        self._prop_manager.padding = value
    @property
    def padding_left(self):
        return self._prop_manager.padding_left
    @padding_left.setter
    def padding_left(self, value):
        self._update_copy_opt('padding_left', value)
        self._prop_manager.padding_left = value
    @property
    def padding_right(self):
        return self._prop_manager.padding_right
    @padding_right.setter
    def padding_right(self, value):
        self._update_copy_opt('padding_right', value)
        self._prop_manager.padding_right = value
    @property
    def padding_top(self):
        return self._prop_manager.padding_top
    @padding_top.setter
    def padding_top(self, value):
        self._update_copy_opt('padding_top', value)
        self._prop_manager.padding_top = value
    @property
    def padding_bottom(self):
        return self._prop_manager.padding_bottom
    @padding_bottom.setter
    def padding_bottom(self, value):
        self._update_copy_opt('padding_bottom', value)
        self._prop_manager.padding_bottom = value

    @property
    def spacings(self):
        return self._prop_manager.spacings
    @spacings.setter
    def spacings(self, value):
        self._update_copy_opt('spacings', value)
        self._prop_manager.spacings = value

    @property
    def background(self):
        return self._prop_manager.background
    @background.setter
    def background(self, value):
        self._update_copy_opt('background', value)
        self._prop_manager.background = value

    @property
    def colorbar(self):
        return self._prop_manager.colorbar
    @colorbar.setter
    def colorbar(self, value):
        self._update_copy_opt('colorbar', value)
        self._prop_manager.colorbar = value

    def _prep_show(self, parent=False):
        super().prep_show()
        if parent:
            if self.plot_legend or any(hasattr(c, 'plot_legend') and c.plot_legend for c in self.children):
                pls = self.plot_legend
                ls = self.legend_style if self.legend_style is not None else {}
                PlotLegend.check_styles(ls)
                if isinstance(pls, PlotLegend):
                    self.axes.legend(handles=pls, **pls.opts, **ls)
                else:
                    self.axes.legend(**ls)
            if 'ticks' in self._init_opts:
                self.ticks = self._init_opts['ticks']
    def prep_show(self):
        if self.figure_parent is self:
            self._prep_show(parent=True)
            for c in self.children:
                if hasattr(c, '_prep_show'):
                    c._prep_show(parent=False)
                else:
                    c.prep_show()
        else:
            self.figure_parent.prep_show()
        return self

    def get_padding_offsets(self):
        ((l, r), (b, t)) = self.plot_range
        # w, h = self.image_size
        ((pl, pr), (pb, pt)) = self.padding
        ((alx, aby), (arx, aty)) = self.axes.bbox.get_points()
        w = arx - alx
        h = aty - aby
        pix_rat_x = (r - l) / w  # pixel to image coordiantes
        pix_rat_y = (t - b) / h
        ofl = pix_rat_x * (pl + self._axes_padding_offset[0])
        ofr = pix_rat_x * pr
        ofb = pix_rat_y * (pb + self._axes_padding_offset[1])
        oft = pix_rat_y * pt
        return [(ofl, ofr), (ofb, oft)]
    def get_bbox(self):
        # gives _effective_ image coordinates for the total space taken up by the figure
        plr = self.plot_range
        (ofl, ofr), (ofb, oft) = self.get_padding_offsets()
        return [
            (plr[0][0] - ofl, plr[1][0] - ofb),
            (plr[0][1] + ofr, plr[1][1] + oft)
        ]
    inset_options = dict(
        image_size='auto',
        background='#FFFFFF00',
        frame_style={'linewidth':0},#, 'color':'#FFFFFF00'},
        aspect_ratio='auto',
        ticks=[[], []],
        ticks_style={'width':0},
        ticks_label_style=[{'fontsize':0}, {'fontsize':0}]
    )
    def create_inset(self, bbox, coordinates='absolute', graphics_class=None, **opts):
        if coordinates == 'absolute':
            ((lx, rx), (by, ty)) = self.plot_range
            w = rx - lx
            h = ty - by
            ((blx, bby), (brx, bty)) = bbox
            slx = ((blx - lx)) / w
            srx = ((brx - lx)) / w
            sby = ((bby - by)) / h
            sty = ((bty - by)) / h
            bbox = [(slx, sby), (srx, sty)]

            # ((ofl, ofr), (ofb, oft)) = self.get_padding_offsets()
            # w = (rx - lx + ofr + ofl)
            # h = (ty - by + oft + ofb)
            # # get coordinates relative to plot frame
            # ((blx, bby), (brx, bty)) = bbox
            # slx = (ofl+(blx-lx))/w
            # srx = (ofl+(brx-lx))/w
            # sby = (ofb+(bby-by))/h
            # sty = (ofb+(bty-by))/h
            # bbox = [(slx, sby), (srx, sty)]
            # # now shift to position of axis in total image
            coordinates = 'scaled'
        if graphics_class is None:
            graphics_class = Graphics
        return super().create_inset(bbox, coordinates=coordinates, graphics_class=graphics_class, **opts)

########################################################################################################################
#
#                                               Graphics3D
#
class Graphics3D(Graphics):
    """Extends the standard matplotlib 3D plotting to use all the Graphics extensions"""

    opt_keys = GraphicsBase.opt_keys | {'view_settings'}
    known_keys = Graphics.opt_keys | {'animate'}

    # layout_keys = axes_keys | figure_keys | GraphicsBase.layout_keys
    # known_keys = layout_keys

    def __init__(self, *args,
                 figure=None,
                 axes=None,
                 subplot_kw=None,
                 event_handlers=None,
                 animate=None,
                 axes_labels=None,
                 plot_label=None,
                 plot_range=None,
                 plot_legend=None,
                 ticks=None,
                 scale=None,
                 ticks_style=None,
                 image_size=None,
                 background=None,
                 view_settings=None,
                 backend=Backends.MPL,
                 **kwargs
                 ):

        self._backend = backend
        super().__init__(
            *args,
            figure=figure,
            axes=axes,
            subplot_kw=subplot_kw,
            axes_labels=axes_labels,
            plot_label=plot_label,
            plot_range=plot_range,
            plot_legend=plot_legend,
            ticks=ticks,
            scale=scale,
            ticks_style=ticks_style,
            image_size=image_size,
            event_handlers=event_handlers,
            animate=animate,
            view_settings=view_settings,
            prop_manager=GraphicsPropertyManager3D,
            **kwargs
        )

    def set_options(self,
                    view_settings=None,
                    **parent_opts
                    ):

        super().set_options(**parent_opts)

        opts = (
            ('view_settings', view_settings),
        )
        for oname, oval in opts:
            oval = self._get_def_opt(oname, oval, {})
            if oval is not None:
                setattr(self, oname, oval)

    def load_mpl(self):
        if not self._mpl_loaded:
            from mpl_toolkits.mplot3d import Axes3D
            super().load_mpl()

    @staticmethod
    def _subplot_init(*args, backend=Backends.MPL, mpl_backend=None, figure=None, **kw):
        if backend == Backends.VTK:
            from .VTKInterface import VTKWindow
            window = VTKWindow()
            return window, window
        else:
            with figure.pyplot as plt:
                subplot_kw = {"projection": '3d'}
                if 'subplot_kw' in kw:
                    subplot_kw = dict(subplot_kw, **kw['subplot_kw'])
                    del kw['subplot_kw']
                return plt.subplots(*args, subplot_kw=subplot_kw, **kw)

    def _init_suplots(self, figure, axes, *args, **kw):
        """matplotlib subplot instantiation

        :param figure:
        :type figure:
        :param axes:
        :type axes:
        :param args:
        :type args:
        :param kw:
        :type kw:
        :return:
        :rtype:
        """

        if figure is None:
            figure, axes = self._subplot_init(*args, backend=self._backend, mpl_backend=self.mpl_backend, figure=self, **kw)
        elif isinstance(figure, GraphicsBase) or all(hasattr(figure, h) for h in ['layout_keys']):
            if axes is None:
                axes = figure.axes
            figure = figure.figure

        if axes is None:
            if self._backend == Backends.MPL:
                axes = figure
            else:
                if not hasattr(figure, 'add_subplot'):
                    figure = figure.figure
                if not hasattr(figure, 'axes'):
                    axes = figure.add_subplot(1, 1, 1, projection='3d')
                else:
                    axes = figure.axe

        return figure, axes

    @property
    def plot_label(self):
        return self._prop_manager.plot_label
    @plot_label.setter
    def plot_label(self, value):
        self._prop_manager.plot_label = value

    @property
    def plot_legend(self):
        return self._prop_manager.plot_legend
    @plot_legend.setter
    def plot_legend(self, value):
        self._prop_manager.plot_legend = value

    @property
    def axes_labels(self):
        return self._prop_manager.axes_labels
    @axes_labels.setter
    def axes_labels(self, value):
        self._prop_manager.axes_labels = value

    @property
    def frame(self):
        return self._prop_manager.frame
    @frame.setter
    def frame(self, value):
        self._prop_manager.frame = value

    @property
    def plot_range(self):
        return self._prop_manager.plot_range
    @plot_range.setter
    def plot_range(self, value):
        self._prop_manager.plot_range = value

    @property
    def ticks(self):
        return self._prop_manager.ticks
    @ticks.setter
    def ticks(self, value):
        self._prop_manager.ticks = value

    @property
    def ticks_style(self):
        return self._prop_manager.ticks_style
    @ticks_style.setter
    def ticks_style(self, value):
        self._prop_manager.ticks_style = value

    @property
    def ticks_label_style(self):
        return self._prop_manager.ticks_label_style
    @ticks_label_style.setter
    def ticks_label_style(self, value):
        self._prop_manager.ticks_label_style = value

    @property
    def scale(self):
        return self._prop_manager.ticks
    @scale.setter
    def scale(self, value):
        self._prop_manager.scale = value

    @property
    def aspect_ratio(self):
        return self._prop_manager.aspect_ratio
    @aspect_ratio.setter
    def aspect_ratio(self, value):
        self._prop_manager.aspect_ratio = value

    @property
    def image_size(self):
        return self._prop_manager.image_size
    @image_size.setter
    def image_size(self, value):
        self._prop_manager.image_size = value

    @property
    def padding(self):
        return self._prop_manager.padding
    @padding.setter
    def padding(self, value):
        self._prop_manager.padding = value

    @property
    def background(self):
        return self._prop_manager.background
    @background.setter
    def background(self, value):
        self._prop_manager.background = value

    @property
    def colorbar(self):
        return self._prop_manager.colorbar
    @colorbar.setter
    def colorbar(self, value):
        self._prop_manager.colorbar = value

    # set plot ranges
    @property
    def ticks(self):
        return self._prop_manager.ticks
    @ticks.setter
    def ticks(self, value):
        self._prop_manager.ticks = value

    @property
    def box_ratios(self):
        return self._prop_manager.box_ratios
    @box_ratios.setter
    def box_ratios(self, value):
        self._prop_manager.box_ratios = value

    @property
    def view_settings(self):
        return self._prop_manager.view_settings
    @view_settings.setter
    def view_settings(self, value):
        self._prop_manager.view_settings = value

    @property
    def aspect_ratio(self):
        return None#self._aspect_ratio
    @aspect_ratio.setter
    def aspect_ratio(self, ar):
        pass
        # if isinstance(ar, (float, int)):
        #     a, b = self.plot_range
        #     cur_ar = (b[1] - b[0]) / (a[1] - a[0])
        #     targ_ar = ar / cur_ar
        #     self.axes.set_aspect(targ_ar)
        # elif isinstance(ar, str):
        #     self.axes.set_aspect(ar)
        # else:
        #     self.axes.set_aspect(ar[0], **ar[1])

########################################################################################################################
#
#                                               GraphicsGrid
#
class GraphicsGrid(GraphicsBase):
    """
    A class for easily building sophisticated multi-panel figures.
    Robustification work still needs to be done, but the core interface is there.
    Supports themes & direct, easy access to the panels, among other things.
    Builds off of `GraphicsBase`.
    """
    default_style = dict(
        theme='mccoy',
        spacings=(50, 0),
        padding=((50, 10), (50, 10))
    )
    layout_keys = GraphicsBase.layout_keys | {'nrows', 'ncols'}
    known_keys = GraphicsBase.known_keys | {'graphics_class'}
    def __init__(self,
                 *args,
                 nrows=None, ncols=None,
                 graphics_class=Graphics,
                 figure=None,
                 axes=None,
                 subplot_kw=None,
                 _subplot_init=None,
                 mpl_backend = None,
                 subimage_size=(310, 310),
                 subimage_aspect_ratio='auto',
                 padding=None,
                 spacings=None,
                 **opts
                 ):

        if len(args) > 0:
            if len(args) > 1:
                raise ValueError("wat")
            args = args[0]
            if isinstance(args[0], GraphicsBase):
                nr = 1
                nc = len(args)
            else:
                nr = len(args)
                nc = max(len(a) for a in args)

            if nrows is None:
                nrows = nr
            if ncols is None:
                ncols = nc
        if nrows is None:
            nrows = 2
        if ncols is None:
            ncols = 2
        # if mpl_backend is None and platform.system() == "Darwin":
        #     mpl_backend = "TkAgg"
        self.mpl_backend = mpl_backend
        if subplot_kw is None:
            subplot_kw={}
        else:
            subplot_kw={'subplot_kw':subplot_kw}
        subplot_kw.update(dict(
            nrows=nrows, ncols=ncols,
            graphics_class=graphics_class,
            _subplot_init=graphics_class._subplot_init if _subplot_init is None else _subplot_init,
            subimage_size=subimage_size,
            subimage_aspect_ratio=subimage_aspect_ratio,
            padding=padding,
            spacings=spacings
        ))
        super().__init__(
            figure=figure, axes=axes,
            graphics_class=graphics_class,
            subplot_kw=subplot_kw,
            padding=padding,
            spacings=spacings,
            **opts
        )
        self.shape = (nrows, ncols)
        self._colorbar_axis = None # necessary hack for only GraphicsGrid

        if len(args) > 0:
            if isinstance(args[0], GraphicsBase):
                for i, g in enumerate(args):
                    self[0, i] = g
            else:
                for i, r in enumerate(args):
                    for j, g in enumerate(r):
                        self[i, j] = g

    class GraphicsStack:
        def __init__(self, parent, graphics):
            self.parent = parent #type: GraphicsBase
            self.stack = np.empty_like(graphics, dtype=object)
            self.stack[:] = graphics
        def __getitem__(self, item):
            return self.stack[item]
        def __setitem__(self, item, value):
            self.stack[item] = value
        def _call_iter(self, attr):
            @functools.wraps(getattr(Graphics, attr))
            def call(*args, **kwargs):
                for ax in self.stack.flat:
                    getattr(ax, attr)(*args, **kwargs)
            return call
        def _axes_call_iter(self, attr):
            ax = next(self.stack.flat).axes
            @functools.wraps(getattr(ax, attr))
            def call(*args, **kwargs):
                for ax in self.stack.flat:
                    getattr(ax.axes, attr)(*args, **kwargs)
            return call
        def __getattr__(self, attr):
            if attr not in {'stack', 'parent'}: # indicates deletion
                if hasattr(Graphics, attr):
                    return self._call_iter(attr)
                else:
                    return self._axes_call_iter(attr)
        def __iter__(self):
            return self.stack.flat
            # else:
            #     raise AttributeError("{} has no attribute {}".format(Graphics.__name__, attr))

    def _init_suplots(self,
                      figure, axes, *args,
                      nrows=None, ncols=None, graphics_class=None,
                      subplot_kw=None, _subplot_init=None,
                      fig_kw=None, mpl_backend = None,
                      padding = None, spacings=None,
                      subimage_size=None, subimage_aspect_ratio=None,
                      **kw
                      ):
        """Initializes the subplots for the Graphics object

        :param figure:
        :type figure:
        :param axes:
        :type axes:
        :param args:
        :type args:
        :param kw:
        :type kw:
        :return: figure, axes
        :rtype: matplotlib.figure.Figure, matplotlib.axes.Axes
        """
        import matplotlib.figure, matplotlib.axes
        if mpl_backend is not None:
            import matplotlib
            matplotlib.use(mpl_backend)

        if 'image_size' not in kw:
            kw['image_size'] = subimage_size
        else:
            subimage_size = kw['image_size']
        if figure is None:
            padding = self._get_def_opt('padding', padding, {})
            subimage_aspect_ratio = self._get_def_opt('padding', subimage_aspect_ratio, {})
            spacings = self._get_def_opt('spacings', spacings, {})
            if subplot_kw is None:
                subplot_kw = {}
            if fig_kw is None:
                fig_kw = {}
            if 'figsize' not in fig_kw:
                try:
                    dw, dh = subimage_size
                except (ValueError, TypeError):
                    dw = dh = subimage_size
                w = ncols * dw
                h = nrows * dh
                if padding is not None:
                    pw, ph = padding
                    try:
                        pwx, pwy = pw
                    except (TypeError, ValueError):
                        pwx = pwy = pw
                    try:
                        phx, phy = ph
                    except (TypeError, ValueError):
                        phx = phy = ph

                    w += pwx + pwy
                    h += phx + phy
                if spacings is not None:
                    sw, sh = spacings
                    w += (ncols-1) * sw
                    h += (nrows-1) * sh
                fig_kw['figsize'] = (w/72., h/72.)

            figure, axes = _subplot_init(*args, nrows=nrows, ncols=ncols, subplot_kw=subplot_kw, figure=self, **fig_kw)
            self.add_figure_graphics(figure, self)

            if isinstance(axes, matplotlib.axes.Axes):
                axes = [[axes]]
            elif isinstance(axes[0], matplotlib.axes.Axes):
                if ncols == 1:
                    axes = [[ax] for ax in axes]
                else:
                    axes = [axes]
            if 'aspect_ratio' not in kw:
                kw['aspect_ratio'] = subimage_aspect_ratio
            for i in range(nrows):
                for j in range(ncols):
                    axes[i][j] = graphics_class(figure=figure, axes=axes[i][j], managed=True, **kw)
            axes = self.GraphicsStack(self, axes)
        elif isinstance(figure, GraphicsGrid):
            axes = figure.axes  # type: matplotlib.axes.Axes
            figure = figure.figure  # type: matplotlib.figure.Figure

        if axes is None:
            axes = self.GraphicsStack(self, [
                graphics_class(
                    figure.add_subplot(nrows, ncols, i),
                    managed=True,
                    **kw
                ) for i in range(nrows * ncols)
            ])

        return figure, axes

    def set_options(self,
                    padding=None,
                    spacings=None,
                    background=None,
                    colorbar=None,
                    figure_label=None,
                    **parent_opts
                    ):

        super().set_options(**parent_opts)

        self.image_size = None
        opts = (
            ('figure_label', figure_label),
            ('padding', padding),
            ('spacings', spacings),
            ('background', background),
            ('colorbar', colorbar)
        )
        for oname, oval in opts:
            oval = self._get_def_opt(oname, oval, {})
            if oval is not None:
                setattr(self, oname, oval)

    def __iter__(self):
        return iter(self.axes)
    def __getitem__(self, item):
        try:
            i, j = item
        except ValueError:
            return self.axes[item]
        else:
            return self.axes[i][j]
    def __setitem__(self, item, val):
        try:
            i, j = item
        except ValueError:
            if isinstance(val, GraphicsBase):
                self.axes[item] = val.change_figure(self.axes[item], image_size=val.image_size)
            else:
                self.axes[item] = val
        else:
            if isinstance(val, GraphicsBase):
                self.axes[i][j] = val.change_figure(self.axes[i][j], image_size=val.image_size)
            else:
                self.axes[i][j] = val
    def set_image(self, pos, val, **opts):
        pos = tuple(pos) if not isinstance(pos, int) else pos
        self.axes[pos] = val.change_figure(self.axes[pos], image_size=val.image_size, **opts)
        return self.axes[pos]

    # def __getattr__(self, item):
    #     reraise_error = None
    #     figure = object.__getattribute__(self, 'figure')
    #     try:
    #         meth = getattr(figure, item)
    #     except AttributeError as e:
    #         if 'Figure' not in e.args[0] or item not in e.args[0]:
    #             reraise_error = e
    #         else:
    #             reraise_error = AttributeError("'{}' object has no attribute '{}'".format(
    #                 type(self).__name__,
    #                 item
    #             ))
    #
    #     if reraise_error is not None:
    #         raise reraise_error
    #
    #     return meth

    # set size
    def calc_image_size(self):
        w=0; h=0
        for l in self.axes.stack:
            mh = 0; mw = 0
            for f in l:
                wh = f.image_size
                if wh is not None:
                    mw += wh[0]
                    if wh[1] > mh:
                        mh = wh[1]
            h += mh
            if w < mw:
                w = mw

        spacings = self.spacings
        if spacings is not None:
            sw, sh = spacings
            w += (self.shape[1] - 1) * sw
            h += (self.shape[0] - 1) * sh

        pad = self.padding
        if pad is not None:
            pw, ph = pad
            w += pw[0] + pw[1]
            h += ph[0] + ph[1]
        return w, h

    @property
    def image_size(self):
        s = self.calc_image_size()
        self._prop_manager.image_size = s
        return s
    @image_size.setter
    def image_size(self, value):
        s = self.calc_image_size()
        self._prop_manager.image_size = s

    @property
    def figure_label(self):
        return self._prop_manager.figure_label
    @figure_label.setter
    def figure_label(self, value):
        self._prop_manager.figure_label = value
    @property
    def padding(self):
        return self._prop_manager.padding
    @padding.setter
    def padding(self, value):
        self._prop_manager.padding = value
    @property
    def padding_left(self):
        return self._prop_manager.padding_left
    @padding_left.setter
    def padding_left(self, value):
        self._prop_manager.padding_left = value
    @property
    def padding_right(self):
        return self._prop_manager.padding_right
    @padding_right.setter
    def padding_right(self, value):
        self._prop_manager.padding_right = value
    @property
    def padding_top(self):
        return self._prop_manager.padding_top
    @padding_top.setter
    def padding_top(self, value):
        self._prop_manager.padding_top = value
    @property
    def padding_bottom(self):
        return self._prop_manager.padding_bottom
    @padding_bottom.setter
    def padding_bottom(self, value):
        self._prop_manager.padding_bottom = value

    @property
    def spacings(self):
        return self._prop_manager.spacings
    @spacings.setter
    def spacings(self, value):
        self._prop_manager.spacings = value

    @property
    def background(self):
        return self._prop_manager.background
    @background.setter
    def background(self, value):
        self._prop_manager.background = value

    @property
    def colorbar(self):
        return self._prop_manager.colorbar
    @colorbar.setter
    def colorbar(self, value):
        self._prop_manager.colorbar = value

    # def _prep_show(self):
    #     self.image_size = None
    #     if self.spacings is not None:
    #         self.spacings = self.spacings
    #     if self.padding is not None:
    #         self.padding = self.padding
    #     if self.tighten:
    #         self.figure.tight_layout()
        # super().prep_show()

    def _prep_show(self, parent=False):
        self.image_size = None
        if self.spacings is not None:
            self.spacings = self.spacings
        if self.padding is not None:
            self.padding = self.padding
        if self.tighten:
            self.figure.tight_layout()
    def prep_show(self):
        if self.figure_parent is self:
            self._prep_show(parent=True)
            for c in self.children:
                if hasattr(c, '_prep_show'):
                    c._prep_show(parent=False)
                else:
                    c.prep_show()
        else:
            self.figure_parent.prep_show()
        return self

    # def show(self, **kwargs):
    #     super().show(**kwargs)


"""create Post model

Revision ID: 5796a5eb7b81
Revises: fef0b6e1d1a0
Create Date: 2019-01-23 16:00:53.299784

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql

# revision identifiers, used by Alembic.
revision = '5796a5eb7b81'
down_revision = 'fef0b6e1d1a0'
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('post',
                    sa.Column('id', sa.Integer(), nullable=False),
                    sa.Column('writer_id', sa.Integer(), nullable=False),
                    sa.Column('board_id', sa.Integer(), nullable=False),
                    sa.Column('title', sa.String(length=240), nullable=False),
                    sa.Column('body', sa.Text(), nullable=True),
                    sa.Column('is_published', sa.Boolean(), nullable=False, default=True),
                    sa.Column('like_count', sa.Integer(), nullable=False),
                    sa.Column('view_count', sa.Integer(), nullable=False),
                    sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
                    sa.Column('updated_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
                    sa.ForeignKeyConstraint(['board_id'], ['board.id'], ),
                    sa.ForeignKeyConstraint(['writer_id'], ['user.id'], ),
                    sa.PrimaryKeyConstraint('id')
                    )
    op.alter_column('post', 'is_published',
                    existing_type=mysql.TINYINT(display_width=1),
                    type_=sa.Boolean(),
                    nullable=False)
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_table('post')
    # ### end Alembic commands ###


# -*- coding: utf-8 -*-
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response

from django.db.models import Sum, Q, F, Count, TextField
from django.db.models.functions import Cast

from django.utils.safestring import mark_safe
from django.utils.numberformat import format

from django.contrib.auth.models import User, Group, UserManager

import re, datetime

from django import template

register = template.Library()

from docflow.models import *

@register.simple_tag
def rest_sum(obj):
	voucher = cash_payment_voucher.objects.get(id=obj)
	res = voucher.moneyflow.value
	for cr in voucher.cash_report_set.all():
		res = res - cr.sum
	return res

@register.simple_tag
def count_sum(obj):
	voucher = cash_payment_voucher.objects.get(id=obj)
	res = 0
	for cr in voucher.cash_report_set.all():
		res = res + cr.sum
	return res

@register.simple_tag
def total_cash_report_sum(user):
	vouchers_sum_pull = 0
	vouchers_sum_push = 0
	cash_reports_sum = 0
	# расход (получено из кассы)
	vouchers_sum_pull = sum(cash_payment_voucher.objects.filter(addressee__user=user).filter(type='pull').values_list('moneyflow__value',flat=True))
	# приход (возвращено в кассу)
	vouchers_sum_push = sum(cash_payment_voucher.objects.filter(addressee__user=user).filter(type='push').values_list('moneyflow__value',flat=True))
	# отчеты (обосновано документами)
	cash_reports_sum = sum(cash_report.objects.filter(user__user=user).filter(status='approved').values_list('sum',flat=True))
	# итоговый баланс
	res = 0
	#
	if vouchers_sum_pull or cash_reports_sum or vouchers_sum_push:
		res = vouchers_sum_pull - vouchers_sum_push - cash_reports_sum

	return [res, vouchers_sum_pull, vouchers_sum_push, cash_reports_sum]

@register.filter(name='projecstepexecutor')
def projecstepexecutor(value, arg):
	return value.filter(executor__id=arg)

@register.inclusion_tag('docflow_my_finance_inc.html', takes_context=True)
def docflow_my_finance_inc(context, **kwargs):
	request = context['request']
	#
	try:
		user=kwargs['user']
	except:
		user=request.user
	#
	total_balance = 0
	total_balance_forecast = 0
	vouchers_sum_pull = 0
	vouchers_sum_push = 0
	cash_reports_sum = 0
	cash_reports_sum_wait = 0
	cash_reports_sum_all = 0

	"""#расход (получено из кассы)"""
	vouchers_sum_pull = sum(cash_payment_voucher.objects.filter(addressee__user=user).filter(type='pull').values_list('moneyflow__value', flat=True))
	"""#"""
	"""#приход (возвращено в кассу)"""
	vouchers_sum_push = sum(cash_payment_voucher.objects.filter(addressee__user=user).filter(type='push').values_list('moneyflow__value',flat=True))
	"""#"""
	"""#отчеты (обосновано документами)"""
	cash_reports_sum = sum(cash_report.objects.filter(user__user=user).filter(status='approved').values_list('sum',flat=True))
	"""#"""
	"""#отчеты в ожидании"""
	cash_reports_sum_wait = sum(cash_report.objects.filter(user__user=user).filter(status='created').values_list('sum',flat=True))
	"""#"""
	"""#всего отчетов"""
	cash_reports_sum_all = sum(cash_report.objects.filter(user__user=user).filter().values_list('sum',flat=True))
	"""#"""

	"""#total_balance"""
	if vouchers_sum_pull or cash_reports_sum or vouchers_sum_push:
		total_balance = vouchers_sum_pull - vouchers_sum_push - cash_reports_sum
	"""#"""
	"""#Прогноз баланса"""
	total_balance_forecast = total_balance - cash_reports_sum_wait
	"""#"""

	return {
		'total_balance': total_balance,
		'total_balance_forecast': total_balance_forecast,
		'vouchers_sum_pull': vouchers_sum_pull, 'vouchers_sum_push': vouchers_sum_push,
		'cash_reports_sum': cash_reports_sum,
		'cash_reports_sum_wait': cash_reports_sum_wait,
		'cash_reports_sum_all': cash_reports_sum_all,
		}


import os, re
from baseinterface import BaseInterface, needsidle
from threadedwirelessinterface import ThreadedWirelessInterface as WirelessInterface
from threadedwirelessinterface import AsyncError
from misc import WicdError
import logging
def needscurrentnetwork(method):
    '''
    Can be used as a decorator to raise an
    error if the interface is not idle.
    '''
    def profilecheck(self, *args, **kwargs):
        if not hasattr(self.interface, 'current_network'):
            raise WicdError('Must run set_current_profile() before %s()' %
                            method.func_name)
        return method(self, *args, **kwargs)
    profilecheck.func_name = method.func_name
    return profilecheck
class BackendWirelessInterface(BaseInterface):
    ''' A Wireless Interface backend for Wicd. '''
    @staticmethod
    def get_type():
        ''' Returns type of network interface. '''
        return 'wireless'
    @staticmethod
    def find_available_interfaces():
        ''' Returns list of strings of valid network interface names. '''
        dev_dir = '/sys/class/net/'
        ifnames = [iface for iface in os.listdir(dev_dir) \
                   if os.path.isdir(dev_dir + iface)
                   and 'wireless' in os.listdir(dev_dir + iface)]
        return ifnames
    def __init__(self, interface_name, status_change_callback):
        ''' Instantiates the BackendWirelessInterface. '''
        BaseInterface.__init__(self, interface_name, status_change_callback)
        if not interface_name in self.find_available_interfaces():
            raise self.CannotCreateInterfaceException(
                'No wireless interface named %s exists.' % interface_name)
        self.name = 'Wireless Interface %s' % self.interface_name
        self.interface = WirelessInterface(self.interface_name)
        self._status_change('idle')
    @needsidle
    def do_update(self):
        """ Updates interface status. """
        self._status_change('updating')
        self._status_change('idle')
    @needsidle
    def do_scan(self):
        ''' Scans for new networks. '''
        def finished_callback():
            ''' Sets the current interface state. '''
            self._status_change('idle')
        self.interface.scan(finished_callback)
        self._status_change('scanning')
    @needsidle
    def get_networks(self):
        """ Returns a dictionary of network BSSIDs with the ESSIDs. """
        try:
            networks = self.interface.networks
        except AttributeError:
            raise WicdError('Must run do_scan() before get_networks()')
        networks = dict( [ ( network.bssid, network.essid ) for network in networks ] )
        return networks
    def _get_network(self, the_property, value):
        ''' Returns the first network where property == value. '''
        if not hasattr(self.interface, 'networks'):
            raise WicdError('Must run do_scan() before _get_networks()')
        for network in self.interface.networks:
            if hasattr(network, the_property):
                if getattr(network, the_property) == value:
                    return network
        raise WicdError('No network with the specified %s exists.' % the_property)
    def get_has_profile_property(self, bssid, name):
        network = self._get_network('bssid', bssid)
        return name in network.profile
    @needsidle
    def set_profile_property(self, bssid, name, value):
        network = self._get_network('bssid', bssid)
        network.profile[name] = value
    def get_profile_property(self, bssid, name):
        network = self._get_network('bssid', bssid)
        if name in network.profile:
            return network.profile[name]
        else:
            raise WicdError('%s: no such profile property' % name)
    def get_network_information(self, bssid, name):
        network = self._get_network('bssid', bssid)
        if hasattr(network, name):
            return getattr(network, name)
        else:
            raise WicdError('%s: network has no such information' % name)
    @needsidle
    def do_connect(self, bssid):
        ''' Connects to the wireless network set with set_current_profile(). '''
        def finished_callback(result):
            ''' Sets the current interface state. '''
            self._state_change(result)
            self._status_change('idle')
        self._status_change('connecting')
        network = self._get_network('bssid', bssid)
        self.interface.connect(network, finished_callback)
    def do_cancel_connect(self):
        ''' Cancels the current connection attempt. '''
        self.interface.cancel_connection_attempt()
    @needsidle
    def do_save(self):
        ''' Saves the wireless profiles. '''
        self.interface.save_profiles()
    @needsidle
    def do_autoconnect(self, value):
        logging.debug('%s: do_autoconnect %s', self.interface.interface_name, value)
        for network in self.interface.networks:
            logging.debug('found network %s', network.essid)
            if 'autoconnect' in network.profile \
               and network.profile['autoconnect']:
                logging.debug('autoconnecting to %s', network.essid)
                self.do_connect(network.bssid)
                return


import math
import collections
import heapq
import bisect
import functools

def solve(a, n):
    pass

if __name__ == '__main__':
    n, q = map(int, input().split())
    a = list(map(int, input().split()))
    d = {}
    for i, x in enumerate(a):
        if x not in d:
            d[x] = []
        d[x].append(i)
    for _ in range(q):
        i, z, direct = input().split()
        i, z = int(i), int(z)
        if z not in d:
            print(-1)
            continue
        if direct == "R":
            j = bisect.bisect_left(d[z], i)
            if j < len(d[z]):
                print(d[z][j] - i)
                continue
            else:
                print(n - i + d[z][0])
        else:
            j = bisect.bisect_right(d[z], i) - 1
            if j >= 0:
                print(i - d[z][j])
                continue
            else:
                print(i + n - d[z][-1])


import stem.process
import time
def __logsTorInstance(log):
	print (log)

torConfig = {'ControlPort': '9151', 'SocksPort' : '5000'}
torProcess = stem.process.launch_tor_with_config(config = torConfig, tor_cmd="/usr/bin/tor", init_msg_handler=__logsTorInstance)
time.sleep(5)
print (torProcess)
#if int(torProcess) > 0:
#	print ("TOR Process PID %s " % (torProcess.pid))


from solution import print_depth, Person
import unittest
from unittest import mock
import io
import sys

class TestSolution(unittest.TestCase):
    """
    Test the solution for problem 1
    """

    @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    def assert_stdout(self, input_val, expected_output, mock_stdout):
        print_depth(input_val)
        self.assertEqual(mock_stdout.getvalue(), expected_output)

    def test_dictionary_with_object(self):
        person_a = Person("User", "1", None)
        person_b = Person("User", "2", person_a)

        a = {
            "key1": 1,
            "key2": {
                "key3": 1,
                "key4": {
                    "key5": 4,
                    "user": person_b
                }
            }
        }
        output = "key1 : 1\nkey2 : 1\nkey3 : 2\nkey4 : 2\nkey5 : 3\nuser : 3\nfather : 4\nfirst_name : 4\nlast_name : 4\nfather : 5\nfirst_name : 5\nlast_name : 5\n"
        self.assert_stdout(a, output)

if __name__ == '__main__':
    unittest.main()


import string
from twitter_specials import *
import math
import csv
import json
labels = {"positive":0,"negative":1,"neutral":2,"irrelevant":3}
reversed_labels = {0:"positive",1:"negative",2:"neutral",3:"irrelevant"}
WCOUNT = 4
total = [0,0,0,0,0]
word_counts_dict={}
word_prob_dict = {}
def parsing():
    with open("data/labeled_corpus.tsv",encoding="utf-8") as csvfile:
        readCSV = csv.reader(csvfile, delimiter='\t')
        for row in readCSV:
            line_arr = list(row)
            tweet = line_arr[0]
            tweet = clean_tweet(tweet, emo_repl_order, emo_repl, re_repl)
            words = tweet.split()
            word_set = set()
            punc = set(string.punctuation)
            label = str(line_arr[1])
            if label not in labels:
                continue
            for w in words:
                if '#' not in w and '@' not in w:
                    s = ""
                    for ch in w:
                        if (ch not in punc):
                            s += ch
                    w = s
                    word_set.add(w)

            for w in word_set:
                if w not in word_counts_dict:
                    word_counts_dict[w] = [0,0,0,0,0]
                word_counts_dict[w][WCOUNT]+= 1
                word_counts_dict[w][labels[label]]+=1
            total[WCOUNT]+=1
            total[labels[label]] += 1
        for w,counts in word_counts_dict.items():
            for i in range(4):
                if w not in word_prob_dict:
                    word_prob_dict[w] = [0,0,0,0]
                word_prob_dict[w][i]=word_counts_dict[w][i]/total[i]
    csvfile.close()

def classifier():
    with open("data/geo_twits_squares.tsv", encoding="utf-8") as csvfile:
        readCSV = csv.reader((line.replace('\0', '') for line in csvfile), delimiter='\t')
        output = []
        for row in readCSV:
            line_arr = list(row)
            latitude = line_arr[0]
            longitude = line_arr[1]
            prob = [0,0,0,0]
            tweet = clean_tweet(line_arr[2], emo_repl_order, emo_repl, re_repl)
            words = tweet.split()
            punc = set(string.punctuation)
            tested = 0
            for w in words:
                if '#' not in w and '@' not in w:
                    s = ""
                    for ch in w:
                        if (ch not in punc):
                            s += ch
                    w = s
                    if w in word_prob_dict:
                        tested = 1
                        try:
                            for i in range(4):
                                prob[i] = math.log(word_prob_dict[w][i])+math.log(total[i]/total[WCOUNT])
                        except:pass
            if tested == 1:
                row_output = [latitude,longitude, reversed_labels[prob.index(max(prob))]]
            else : row_output = [latitude,longitude, "irrelevant"]
            output.append(row_output)
        with open("locations_classified.tsv","w") as tsvfile:
            writer = csv.writer(tsvfile, delimiter='\t')
            writer.writerows(output)
    tsvfile.close()
    csvfile.close()
pos_dict = {}
def pscore():
    pos_dict = {}
    locations = None
    with open("locations_classified.tsv", encoding="utf-8") as csvfile:
        readCSV = csv.reader(csvfile, delimiter='\t')
        for row in readCSV:
            line_arr = list(row)
            label = line_arr[2]
            if (line_arr[0], line_arr[1]) not in pos_dict:
                if locations != None:
                    pos_dict[locations] = (count[0]/count[-1] - count[1]/count[-1] + 1)/2
                locations = (line_arr[0], line_arr[1])
                count = [0.0, 0.0, 0.0, 0.0, 1.0]
                pos_dict[locations] = 0
                count[labels[label]] += 1
            else:
                count[labels[label]] += 1
                count[-1] += 1
        pos_dict[locations] = (count[0]/count[-1] - count[1]/count[-1] + 1)/2
        #I will do the json file as well here
        input = []
        for loc,score in pos_dict.items():
            dict = {}
            dict["score"]=score
            dict["g"]=float(loc[1])+0.025
            dict["t"]=float(loc[0])+0.025
            input.append(dict)
        with open("data.js","w") as jsonfile:
            jsonfile.write("var data =")
            json.dump(input,jsonfile)
if __name__ == '__main__':
    parsing()
    classifier()
    pscore()


#Vanilla TD Actor-Critic
import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

#Hyperparameters
learning_rate = 0.005
gamma         = 0.99

class ActorCritic(nn.Module):
    def __init__(self):
        super(ActorCritic, self).__init__()
        self.loss_lst = []

        self.fc1 = nn.Linear(4, 128)
        self.fc_pi = nn.Linear(128, 2)
        self.fc_v = nn.Linear(128, 1)
        self.optimizer = optim.Adam(self.parameters(), lr=learning_rate)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        pi = self.fc_pi(x)
        pi = F.softmax(pi, dim=0)
        v = self.fc_v(x)
        return pi, v

    def gather_loss(self, loss):
        self.loss_lst.append(loss.unsqueeze(0))

    def train(self):
        loss = torch.cat(self.loss_lst).sum()
        loss = loss/len(self.loss_lst)
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        self.loss_lst = []

def main():
    env = gym.make('CartPole-v1')
    model = ActorCritic()

    score = 0.0
    print_interval = 20

    for n_epi in range(10000):
        obs = env.reset()
        loss_lst = []
        for t in range(600):
            obs = torch.from_numpy(obs).float()
            pi, v = model(obs)
            m = Categorical(pi)
            action = m.sample()

            obs, r, done, info = env.step(action.item())
            _, next_v = model(torch.from_numpy(obs).float())
            delta = r + gamma * next_v - v
            loss = -torch.log(pi[action]) * delta.item() + delta * delta.item()
            model.gather_loss(loss)

            score += r
            if done:
                break

        model.train()

        if n_epi%print_interval==0 and n_epi!=0:
            print("# of episode :{}, avg score : {:.1f}".format(n_epi, score/print_interval))
            score = 0.0

    env.close()

if __name__ == '__main__':
    main()


import unittest
import os
import numpy as np
import pandas as pd
from late_classifier.features import MHPSExtractor
from late_classifier.features import DetectionsPreprocessorZTF

FILE_PATH = os.path.dirname(os.path.abspath(__file__))
EXAMPLES_PATH = os.path.abspath(os.path.join(FILE_PATH, "../data"))

class TestMHPSExtractor(unittest.TestCase):
    def setUp(self) -> None:
        np.random.seed(0)

        self.objects = pd.DataFrame(
            index=[
                'ZTF18abakgtm',
                'ZTF18abvvcko',
                'ZTF17aaaaaxg',
                'ZTF18aaveorp'
            ],
            columns=['corrected'],
            data=np.random.choice(
                a=[False, True],
                size=(4,)
            )
        )
        preprocess_ztf = DetectionsPreprocessorZTF()

        raw_det_ZTF18abakgtm = pd.read_csv(
            os.path.join(EXAMPLES_PATH, 'ZTF18abakgtm_det.csv'), index_col="oid")
        raw_det_ZTF18abakgtm['sigmapsf_corr_ext'] = raw_det_ZTF18abakgtm['sigmapsf_corr']

        raw_det_ZTF18abvvcko = pd.read_csv(
            os.path.join(EXAMPLES_PATH, 'ZTF18abvvcko_det.csv'), index_col="oid")
        raw_det_ZTF18abvvcko['sigmapsf_corr_ext'] = raw_det_ZTF18abvvcko['sigmapsf_corr']

        raw_det_ZTF17aaaaaxg = pd.read_csv(
            os.path.join(EXAMPLES_PATH, 'ZTF17aaaaaxg_det.csv'), index_col="oid")
        raw_det_ZTF17aaaaaxg['sigmapsf_corr_ext'] = raw_det_ZTF17aaaaaxg['sigmapsf_corr']

        raw_det_ZTF18aaveorp = pd.read_csv(
            os.path.join(EXAMPLES_PATH, 'ZTF18aaveorp_det.csv'), index_col="oid")
        raw_det_ZTF18aaveorp['sigmapsf_corr_ext'] = raw_det_ZTF18aaveorp['sigmapsf_corr']

        keys = [
            'mjd',
            'fid',
            'magpsf_corr',
            'sigmapsf_corr_ext',
            'isdiffpos',
            'magpsf',
            'sigmapsf',
            'ra',
            'dec',
            'sgscore1',
            'rb'
        ]
        self.detections = pd.concat(
            [raw_det_ZTF17aaaaaxg[keys],
             raw_det_ZTF18abvvcko[keys],
             raw_det_ZTF18abakgtm[keys],
             raw_det_ZTF18aaveorp[keys]],
            axis=0
        )
        self.detections = preprocess_ztf.get_magpsf_ml(
            self.detections,
            objects=self.objects
        )

        self.detections = self.detections[[
            'mjd', 'fid', 'magpsf_ml', 'sigmapsf_ml']]

    def test_many_objects(self):
        mhps_extractor = MHPSExtractor()
        mhps_result = mhps_extractor.compute_features(self.detections)
        self.assertEqual((4, 10), mhps_result.shape)
        self.assertEqual(2, len(mhps_result.dropna()))

if __name__ == '__main__':
    unittest.main()