import numpy as np
import numbers
import warnings
import copy

from typing import List

from pymodaq_utils.warnings import deprecation_msg, user_warning

from pymodaq_data.data import (DataRaw, DataWithAxes, DataToExport, DataCalculated, DataDim,
                               DataSource, DataBase, Axis, NavAxis, DataDistribution, Q_, Unit,
                               )  # imported here for backcompatibility. Will allow also the object serialization
                                  # registration


from pymodaq_utils.serialize.factory import SerializableFactory, SerializableBase

ser_factory = SerializableFactory()


@ser_factory.register_decorator()
class DataActuator(DataRaw):
    """Specialized DataWithAxes set with source as 'raw'.
    To be used for raw data generated by actuator plugins"""
    def __init__(self, *args, **kwargs):
        if len(args) == 0 and 'name' not in kwargs:
            args = ['actuator']
        if 'data' not in kwargs:
            kwargs['data'] = [np.array([0.])]
        elif isinstance(kwargs['data'], numbers.Number):  # useful formatting
            kwargs['data'] = [np.array([kwargs['data']])]
        super().__init__(*args, **kwargs)

    def __repr__(self):
        if self.dim.name == 'Data0D':
            return f'<{self.__class__.__name__} ({self.data[0][0]} {self.units})>'
        else:
            return f'<{self.__class__.__name__} ({self.shape} {self.units})>'

    def __add__(self, other: object):
        if isinstance(other, numbers.Number) and self.length == 1 and self.size == 1:
            new_data = copy.deepcopy(self)
            new_data = new_data + DataActuator(data=other)
            return new_data

        else:
            return super().__add__(other)


@ser_factory.register_decorator()
class DataFromPlugins(DataRaw):
    """Specialized DataWithAxes set with source as 'raw'. To be used for raw data generated by Detector plugins

    It introduces by default to extra attributes, do_plot and do_save. Their presence can be checked in the
    extra_attributes list.

    Parameters
    ----------
    do_plot: bool
        If True the underlying data will be plotted in the DAQViewer
    do_save: bool
        If True the underlying data will be saved

    Attributes
    ----------
    do_plot: bool
        If True the underlying data will be plotted in the DAQViewer
    do_save: bool
        If True the underlying data will be saved
    """
    def __init__(self, *args, **kwargs):

        ##### for backcompatibility
        if 'plot' in kwargs:
            deprecation_msg("'plot' should not be used anymore as extra_attribute, "
                            "please use 'do_plot'")
            do_plot = kwargs.pop('plot')
            kwargs['do_plot'] = do_plot

        if 'save' in kwargs:
            deprecation_msg("'save' should not be used anymore as extra_attribute, "
                            "please use 'do_save'")
            do_save = kwargs.pop('save')
            kwargs['do_save'] = do_save
        #######

        if 'do_plot' not in kwargs:
            kwargs['do_plot'] = True
        if 'do_save' not in kwargs:
            kwargs['do_save'] = True
        super().__init__(*args, **kwargs)


@ser_factory.register_decorator()
class DataScan(DataToExport):
    """Specialized DataToExport.To be used for data to be saved """
    def __init__(self, name: str, data: List[DataWithAxes] = [], **kwargs):
        super().__init__(name, data, **kwargs)


@ser_factory.register_decorator()
class DataToActuators(DataToExport):
    """ Particular case of a DataToExport adding one named parameter to indicate what kind of change
    should be applied to the actuators, absolute or relative

    Attributes
    ----------
    mode: str
        Adds an attribute called mode holding a string describing the type of change:
        relative or absolute

    Parameters
    ---------
    mode: str
        either 'rel' or 'abs' for a relative or absolute change of the actuator's values
    """
    mode: str

    def __init__(self, *args, mode='rel', **kwargs):
        if mode not in ['rel', 'abs']:
            user_warning('Incorrect mode for the actuators, '
                         'switching to default relative mode: rel')
            mode = 'rel'
        kwargs.update({'mode': mode})
        super().__init__(*args, **kwargs)

    def __repr__(self):
        return f'{super().__repr__()}: {self.mode}'
