from pymodaq.control_modules.move_utility_classes import (
    DAQ_Move_base, comon_parameters_fun, main, DataActuatorType, DataActuator)
from pymodaq_utils.utils import ThreadCommand

from pymodaq_gui.parameter import Parameter

from pymodaq_utils.logger import set_logger, get_module_name

logger = set_logger(get_module_name(__file__))


class DAQ_Move_DCServoCube_Abstract(DAQ_Move_base):
    """ Abstract class for Thorlabs DCServo cube instrument plugin.

    This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Move module through inheritance via
    DAQ_Move_base. It makes a bridge between the DAQ_Move module and the Python wrapper of a particular instrument.

    Attributes:
    -----------
    controller: object
        The particular object that allow the communication with the hardware, in general a python wrapper around the
         hardware library.

    """

    # Type of controller used.
    # To redefine in the child class.
    controller_type = None

    # Child class-specific parameters go here.
    # Example:
    #
    #is_multiaxes = False
    #_axes_names = ['']
    #_epsilon = 0.005
    #data_actuator_type = DataActuatorType.DataActuator
    #params = [
    #             {'title': 'Serial Number:', 'name': 'serial_number', 'type': 'list',
    #              'limits': serialnumbers_tcube_dcservo, 'value': serialnumbers_tcube_dcservo[0]}
    #
    #         ] + comon_parameters_fun(is_multiaxes, axes_names=_axes_names, epsilon=_epsilon)

    def ini_attributes(self):
        self.controller: self.controller_type = None
        self._move_done = False


    def move_done_callback(self, val: int):
        """ will be triggered for each end of move: abs, rel or homing"""
        self._move_done = True
        self.stop_motion()
        logger.debug('Callback called')


    def user_condition_to_reach_target(self) -> bool:
        """ Implement a condition for exiting the polling mechanism and specifying that the
        target value has been reached

       Returns
        -------
        bool: if True, PyMoDAQ considers the target value has been reached
        """
        return self._move_done


    def get_actuator_value(self):
        """Get the current value from the hardware with scaling conversion.

        Returns
        -------
        float: The position obtained after scaling conversion.
        """
        pos = DataActuator(
            data=self.controller.get_position(),
            units=self.controller.get_units()
        )
        pos = self.get_position_with_scaling(pos)
        return pos


    def close(self):
        """Terminate the communication protocol"""
        if self.is_master:
            self.controller.close()


    def commit_settings(self, param: Parameter):
        """Apply the consequences of a change of value in the detector settings

        Parameters
        ----------
        param: Parameter
            A given parameter (within detector_settings) whose value has been changed by the user
        """
        pass


    def ini_stage(self, controller=None):
        """Actuator communication initialization

        Parameters
        ----------
        controller: (object)
            custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller (Master case)

        Returns
        -------
        info: str
        initialized: bool
            False if initialization failed otherwise True
        """

        if self.is_master:
            self.controller = self.controller_type()
            self.controller.connect(self.settings['serial_number'])
        else:
            self.controller = controller

        # update the axis unit by interogating the controller and the specific axis
        self.axis_unit = self.controller.get_units()

        if not self.controller.is_homed:
            self.move_home()

        info = f"{self.controller.name} - {self.controller.serial_number}"
        initialized = True
        return info, initialized


    def move_abs(self, value: DataActuator):
        """ Move the actuator to the absolute target defined by value

        Parameters
        ----------
        value: (float) value of the absolute target positioning
        """
        self._move_done = False
        value = self.check_bound(value)
        self.target_value = value
        value = self.set_position_with_scaling(value)  # apply scaling if the user specified one
        self.controller.move_abs(value.value(), callback=self.move_done_callback)


    def move_rel(self, value: DataActuator):
        """ Move the actuator to the relative target actuator value defined by value

        Parameters
        ----------
        value: (float) value of the relative target positioning
        """
        self._move_done = False
        value = self.check_bound(self.current_position + value) - self.current_position
        self.target_value = value + self.current_position
        value = self.set_position_relative_with_scaling(value)
        self.controller.move_rel(value.value(), callback=self.move_done_callback)


    def move_home(self):
        """Call the reference method of the controller"""
        self._move_done = False
        self.controller.home(callback=self.move_done_callback)


    def stop_motion(self):
        """Stop the actuator and emits move_done signal"""
        self.controller.stop()
