# -*- coding: utf-8 -*-
"""
Created on Thu Mar 31 14:58:54 2016

@author: Tobias Jachowski
"""
import numpy as np

from pyoti.modification.modification import Modification
from pyoti import helpers as hp
from pyoti import traces as tc
from pyoti.picklable import unboundfunction


class GenericMod(Modification):
    """
    Generic modification that modifies a set of chosen traces.
    """
    def __init__(self, modify=None, recalculate=None, mod_params=None,
                 print_info=None, **kwargs):
        # modify: function, that modifies the data
        # recalculate: function, which (in automatic mode) is called before
        #               _modify(). Can be used to automatically calculate
        #               mod_params, which are calculated with data obtained
        #               from view_based, i.e. data obtained by
        #               _get_data_based(). (Only called, if self.updated
        #               is False. Self.updated is set to False, upon any change
        #               of the view_based). The mod_params can be used by
        #               _modify().
        # kwargs:
        # traces_apply=None,
        # view_apply=None, # obligatory
        # view_based=None,
        # automatic_switch=False, Option to disable automatic call of
        #           _recalculate() before the call of _modify()
        # datapoints=-1, if>0 creates an iattribute, which is used for
        #                     decimate/average calculation in functions
        #                   _get_data(), _get_data_apply, and _get_data_based()
        #                  and bins determination in calculate_bin_means()

        if modify is None:
            raise TypeError("GenericMod missing required positional argument "
                            "`modify`.")

        # initialize _mod_params before call of super().__init__
        # to make sure __getattr__() doesn't end in a infinite
        # recursion loop
        self._mod_params = []

        super().__init__(**kwargs)

        # register widgets for modifications of traces
        for trace in self.traces_apply:
            self._mod_params.append(trace)
            key = self._key(trace)
            description = ''.join(('Parameter for trace ', tc.label(trace)))
            self.add_iattribute(key, description=description, value=0.0)

        if mod_params is not None:
            mod_params = hp.listify(mod_params)
            for name in mod_params:
                self._mod_params.append(name)
                key = self._key(name)
                description = ''.join(('Parameter ', name))
                self.add_iattribute(key, description=description, value=0.0)

        self._mod = unboundfunction(modify)

        if recalculate is not None:
            self._recalc = unboundfunction(recalculate)

        if print_info is not None:
            self._print_inf = unboundfunction(print_info)

    def _modify(self, data, samples, data_traces, data_index, mod_index):
        return self._mod(self, data, samples, data_traces, data_index,
                         mod_index)

    def _recalculate(self):
        if hasattr(self, '_recalc'):
            self._recalc(self)

    def _print_info(self):
        if hasattr(self, '_print_inf'):
            self._print_inf()

    def _key(self, trace):
        return ''.join(('mod_param_', trace))

    def get_mod_params(self, names=None):
        if names is None:
            # trace should have the same dimension as traces_apply
            names = self._mod_params
        else:
            names = hp.listify(names)

        return np.array([self.iattributes[self._key(name)] for name in names])

    def set_mod_params(self, values, names=None, leave_automatic=False):
        if names is None:
            # trace should have the same dimension as mod_params
            names = self._mod_params
        else:
            names = hp.listify(names)
        values = hp.listify(values)

        for name, value in zip(names, values):
            key = self._key(name)
            self.iattributes.set_value(key, value,
                                       leave_automatic=leave_automatic)

    def __getattr__(self, name):
        """
        Allow attributes to be used as params selections for set/get_params
        """
        if name in self._mod_params:
            key = self._key(name)
        # if key in self.iattributes:
            return self.iattributes[key]
        else:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        # key = self._key(name)
        if not self._p_setattr(name, value):
            if '_mod_params' in self.__dict__ and name in self._mod_params:
                self.set_mod_params(value, name)
            else:
                super().__setattr__(name, value)

    mod_params = property(get_mod_params, set_mod_params)
