#!/usr/bin/env python
# -*- coding:utf-8 -*-


# ############################################################################
#  license :
# ============================================================================
#
#  File :        LavueController.py
#
#  Project :     Lavue Controller
#
# This file is part of Tango device class.
#
# Tango is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Tango is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Tango.  If not, see <http://www.gnu.org/licenses/>.
#
#
#  $Author :      jan.kotanski$
#
#  $Revision :    $
#
#  $Date :        $
#
#  $HeadUrl :     $
# ============================================================================
#            This file is generated by POGO
#     (Program Obviously used to Generate tango Object)
# ############################################################################

import PyTango
import sys
import json


__all__ = ["LavueController", "LavueControllerClass", "main"]

__docformat__ = 'restructuredtext'


if sys.version_info > (3,):
    unicode = str


class LavueController (PyTango.Device_4Impl):
    """tango server to change lavue setting in real time"""

    def __init__(self, cl, name):
        PyTango.Device_4Impl.__init__(self, cl, name)
        self.debug_stream("In __init__()")

        self.current_scalar_aliases = []
        self.current_spectrum_aliases = []
        self.current_rois_aliases = []
        self.current_attributes = set()

        LavueController.init_device(self)

    def delete_device(self):
        self.debug_stream("In delete_device()")

    def init_device(self):
        self.debug_stream("In init_device()")
        self.get_device_properties(self.get_device_class())
        self.attr_BeamCenterX_read = 0.0
        self.attr_BeamCenterY_read = 0.0
        self.attr_DetectorDistance_read = 0.0
        self.attr_DetectorROIs_read = ""
        self.attr_DetectorROIsValues_read = ""
        self.attr_DetectorROIsParams_read = ""
        self.attr_Energy_read = 0.0
        self.attr_LavueState_read = ""
        self.set_change_event("BeamCenterX", True, False)
        self.set_change_event("BeamCenterY", True, False)
        self.set_change_event("DetectorDistance", True, False)
        self.set_change_event("DetectorROIs", True, False)
        self.set_change_event("DetectorROIsValues", True, False)
        self.set_change_event("DetectorROIsParams", True, False)
        self.set_change_event("Energy", True, False)
        self.set_change_event("LavueState", True, False)
        self.attr_DetectorROIs_read = "{}"
        self.attr_DetectorROIsValues_read = "{}"

        self.requested_scalar_aliases = []
        self.requested_spectrum_aliases = []
        self.requested_rois_aliases = []

        if not self.DynamicROIsValues and self.current_scalar_aliases:
            for alias in self.current_scalar_aliases:
                if alias not in self.requested_scalar_aliases:
                    aalias = ("%sSum" % alias) \
                             if alias != "__Null__" else "Sum"
                    if aalias not in self.ROIAttributesNames:
                        self.remove_attribute(str(aalias))
                        if aalias in self.current_attributes:
                            self.current_attributes = \
                                set([at for at in self.current_attributes
                                     if at != aalias])
            self.current_scalar_aliases = []

        if not self.DynamicROIsValues and self.current_spectrum_aliases:
            for alias in self.current_spectrum_aliases:
                if alias not in self.requested_spectrum_aliases:
                    aalias = ("%sSums" % alias) \
                             if alias != "__Null__" else "Sums"
                    if aalias not in self.ROIAttributesNames:
                        self.remove_attribute(str(aalias))
                        if aalias in self.current_attributes:
                            self.current_attributes = \
                                set([at for at in self.current_attributes
                                     if at != aalias])
            self.current_spectrum_aliases = []

        if not self.DynamicROIs and self.current_rois_aliases:
            for alias in self.current_rois_aliases:
                aalias = "%sROI" % alias \
                         if alias != "__Null__" else "ROI"
                if aalias not in self.ROIAttributesNames:
                    self.remove_attribute(str(aalias))
                    if aalias in self.current_attributes:
                        self.current_attributes = \
                            set([at for at in self.current_attributes
                                 if at != aalias])
            self.current_rois_aliases = []

        attrs = []
        for alias in list(self.current_attributes):
            if alias not in self.ROIAttributesNames:
                self.remove_attribute(str(alias))
            else:
                attrs.append(alias)
        self.current_attributes = set(attrs)

        self.initialize_dynamic_attributes()

    def always_executed_hook(self):
        self.debug_stream("In always_excuted_hook()")

    # -------------------------------------------------------------------------
    #    LavueController read/write attribute methods
    # -------------------------------------------------------------------------

    def read_BeamCenterX(self, attr):
        self.debug_stream("In read_BeamCenterX()")
        attr.set_value(self.attr_BeamCenterX_read)

    def write_BeamCenterX(self, attr):
        self.debug_stream("In write_BeamCenterX()")
        data = attr.get_write_value()
        if self.attr_BeamCenterX_read != float(data):
            self.attr_BeamCenterX_read = float(data)
            self.push_change_event("BeamCenterX", self.attr_BeamCenterX_read)

    def read_BeamCenterY(self, attr):
        self.debug_stream("In read_BeamCenterY()")
        attr.set_value(self.attr_BeamCenterY_read)

    def write_BeamCenterY(self, attr):
        self.debug_stream("In write_BeamCenterY()")
        data = attr.get_write_value()
        if self.attr_BeamCenterY_read != float(data):
            self.attr_BeamCenterY_read = float(data)
            self.push_change_event("BeamCenterY", self.attr_BeamCenterY_read)

    def read_DetectorDistance(self, attr):
        self.debug_stream("In read_DetectorDistance()")
        attr.set_value(self.attr_DetectorDistance_read)

    def write_DetectorDistance(self, attr):
        self.debug_stream("In write_DetectorDistance()")
        data = attr.get_write_value()
#
        if self.attr_DetectorDistance_read != float(data):
            self.attr_DetectorDistance_read = float(data)
            self.push_change_event(
                "DetectorDistance", self.attr_DetectorDistance_read)

    def read_LavueState(self, attr):
        self.debug_stream("In read_LavueState()")
        attr.set_value(self.attr_LavueState_read)

    def write_LavueState(self, attr):
        self.debug_stream("In write_LavueState()")
        data = attr.get_write_value()
        detcnf = json.loads(data)
        for key, value in detcnf.items():
            if not isinstance(key, (str, unicode)):
                raise Exception(
                    "state key %s is not a string" % str(key))
        self.attr_LavueState_read = data
        self.push_change_event("LavueState",
                               self.attr_LavueState_read)

    def read_DetectorROIs(self, attr):
        self.debug_stream("In read_DetectorROIs()")
        attr.set_value(self.attr_DetectorROIs_read)

    def write_DetectorROIs(self, attr):
        self.debug_stream("In write_DetectorROIs()")
        data = attr.get_write_value()
        detrois = json.loads(data)
        for det, rois in detrois.items():
            if not isinstance(det, (str, unicode)):
                raise Exception(
                    "ROI name %s is not a string" % str(det))
            if not isinstance(rois, (list)):
                raise Exception(
                    "ROI %s is not a double list" % str(det))
            for roi in rois:
                if not isinstance(roi, (list)):
                    raise Exception(
                        "ROI %s is not a double list" % str(det))
                if len(roi) != 4:
                    raise Exception(
                        "ROI %s len is not 4" % str(det))
                for cr in roi:
                    float(cr)
        if self.DynamicROIs:
            self.requested_rois_aliases = []
            for det, rois in detrois.items():
                self.requested_rois_aliases.append(det.title())

        if self.attr_DetectorROIs_read != data:
            self.attr_DetectorROIs_read = data
            if self.DynamicROIs:
                self.initialize_dynamic_attributes()
            self.push_change_event("DetectorROIs", self.attr_DetectorROIs_read)

    def read_DetectorROIsValues(self, attr):
        self.debug_stream("In read_DetectorROIsValues()")

        attr.set_value(self.attr_DetectorROIsValues_read)

    def write_DetectorROIsValues(self, attr):
        self.debug_stream("In write_DetectorROIsValues()")
        data = attr.get_write_value()
        detrois = json.loads(data)
        for det, rois in detrois.items():

            if not isinstance(det, (str, unicode)):
                raise Exception(
                    "ROI name %s is not a string" % str(det))
            if not isinstance(rois, (list)):
                raise Exception(
                    "ROI %s is not a double list" % str(det))
            for vroi in rois:
                if vroi is not None:
                    float(vroi)

        if self.DynamicROIsValues:
            self.requested_scalar_aliases = []
            self.requested_spectrum_aliases = []
            for det, rois in detrois.items():
                if len(rois) == 1:
                    self.requested_scalar_aliases.append(det.title())
                elif len(rois) > 1:
                    self.requested_spectrum_aliases.append(det.title())

        if self.attr_DetectorROIsValues_read != data:
            self.attr_DetectorROIsValues_read = data
            if self.DynamicROIsValues:
                self.initialize_dynamic_attributes()
            self.push_change_event(
                "DetectorROIsValues", self.attr_DetectorROIsValues_read)

    def read_DetectorROIsParams(self, attr):
        self.debug_stream("In read_DetectorROIsParams()")
        attr.set_value(self.attr_DetectorROIsParams_read)

    def write_DetectorROIsParams(self, attr):
        self.debug_stream("In write_DetectorROIsParams()")
        data = attr.get_write_value()
        if self.attr_DetectorROIsParams_read != str(data):
            self.attr_DetectorROIsParams_read = str(data)
            self.push_change_event(
                "DetectorROIsParams", self.attr_DetectorROIsParams_read)

    def read_Energy(self, attr):
        self.debug_stream("In read_Energy()")
        attr.set_value(self.attr_Energy_read)

    def write_Energy(self, attr):
        self.debug_stream("In write_Energy()")
        data = attr.get_write_value()
        if self.attr_Energy_read != float(data):
            self.attr_Energy_read = float(data)
            self.push_change_event("Energy", self.attr_Energy_read)

    def read_ScalarDynamicAttr(self, attr):
        self.debug_stream("In read_ScalarDynamicAttr()")
        name = attr.get_name()[:-3]
        if not name:
            name = "__null__"
        detrois = json.loads(self.attr_DetectorROIsValues_read)
        if name in detrois.keys() and detrois[name]:
            attr.set_value(detrois[name][0])
        else:
            name = ("%s%s" % (name[0].lower(), name[1:]))
            if name in detrois.keys() and detrois[name]:
                attr.set_value(detrois[name][0])
            else:
                attr.set_value(-1)

    def read_SpectrumDynamicAttr(self, attr):
        self.debug_stream("In read_SpectrumDynamicAttr()")

        name = attr.get_name()[:-4]
        if not name:
            name = "__null__"
        detrois = json.loads(self.attr_DetectorROIsValues_read)
        if name in detrois.keys():
            attr.set_value(detrois[name])
        else:
            name = ("%s%s" % (name[0].lower(), name[1:]))
            if name in detrois.keys():
                attr.set_value(detrois[name])
            else:
                attr.set_value([])

    def read_ROIsDynamicAttr(self, attr):
        self.debug_stream("In read_ROIsDynamicAttr()")

        name = attr.get_name()[:-3]
        if not name:
            name = "__null__"
        detrois = json.loads(self.attr_DetectorROIs_read)
        if name in detrois.keys():
            dtrois = [it for roi in detrois[name] for it in roi]
        else:
            name = ("%s%s" % (name[0].lower(), name[1:]))
            if name in detrois.keys():
                dtrois = [it for roi in detrois[name] for it in roi]
            else:
                dtrois = []
        attr.set_value(dtrois)

    def initialize_dynamic_rois_sums_attributes(self):
        """ initialize rois dynamic attributes
        """
        if self.current_scalar_aliases != self.requested_scalar_aliases or \
           self.current_spectrum_aliases != \
                self.requested_spectrum_aliases or \
                self.ROIAttributesNames:
            for alias in self.current_scalar_aliases:
                if alias not in self.requested_scalar_aliases:
                    aalias = ("%sSum" % alias) \
                             if alias != "__Null__" else "Sum"
                    if aalias not in self.ROIAttributesNames:
                        self.remove_attribute(str(aalias))
            for alias in self.current_spectrum_aliases:
                if alias not in self.requested_spectrum_aliases:
                    aalias = ("%sSums" % alias) \
                             if alias != "__Null__" else "Sums"
                    if aalias not in self.ROIAttributesNames:
                        self.remove_attribute(str(aalias))

            for alias in self.requested_scalar_aliases:
                if alias not in self.current_scalar_aliases:
                    myAttr = PyTango.Attr(
                        str("%sSum" % alias) if alias != "__Null__" else "Sum",
                        PyTango.DevDouble, PyTango.READ)
                    self.add_attribute(
                        myAttr, LavueController.read_ScalarDynamicAttr,
                        None, None)
            for alias in self.requested_spectrum_aliases:
                if alias not in self.current_spectrum_aliases:
                    myAttr = PyTango.SpectrumAttr(
                        str("%sSums" % alias)
                        if alias != "__Null__" else "Sums",
                        PyTango.DevDouble,
                        PyTango.AttrWriteType.READ, 4096)
                    self.add_attribute(
                        myAttr, LavueController.read_SpectrumDynamicAttr,
                        None, None)

            for alias in self.ROIAttributesNames:
                if alias.endswith("Sum"):
                    salias = alias[:-3]
                    if not salias:
                        salias = "__Null__"
                    if salias not in self.current_scalar_aliases:
                        myAttr = PyTango.Attr(
                            str(alias),
                            PyTango.DevDouble, PyTango.READ)
                        self.add_attribute(
                            myAttr, LavueController.read_ScalarDynamicAttr,
                            None, None)
                        self.current_attributes.add(str(alias))
                if alias.endswith("Sums"):
                    salias = alias[:-3]
                    if not salias:
                        salias = "__Null__"
                    if salias not in self.current_spectrum_aliases:
                        myAttr = PyTango.SpectrumAttr(
                            str(alias), PyTango.DevDouble,
                            PyTango.AttrWriteType.READ, 4096)
                        self.add_attribute(
                            myAttr,
                            LavueController.read_SpectrumDynamicAttr,
                            None, None)
                        self.current_attributes.add(str(alias))
            self.current_scalar_aliases = self.requested_scalar_aliases
            self.current_spectrum_aliases = self.requested_spectrum_aliases

    def initialize_dynamic_rois_attributes(self):
        """ initialize rois dynamic attributes
        """
        if self.current_rois_aliases != \
           self.requested_rois_aliases or self.ROIAttributesNames:
            for alias in self.current_rois_aliases:
                if alias not in self.requested_rois_aliases:
                    aalias = "%sROI" % alias \
                             if alias != "__Null__" else "ROI"
                    if aalias not in self.ROIAttributesNames:
                        self.remove_attribute(str(aalias))

            for alias in self.requested_rois_aliases:
                if alias not in self.current_rois_aliases:
                    myAttr = PyTango.SpectrumAttr(
                        str("%sROI" % alias)
                        if alias != "__Null__" else "ROI",
                        PyTango.DevLong,
                        PyTango.AttrWriteType.READ, 4096)
                    self.add_attribute(
                        myAttr, LavueController.read_ROIsDynamicAttr,
                        None, None)
            for alias in self.ROIAttributesNames:
                if alias.endswith("ROI"):
                    salias = alias[:-3]
                    if not salias:
                        salias = "__Null__"
                    if salias not in self.current_rois_aliases:
                        myAttr = PyTango.SpectrumAttr(
                            str(alias), PyTango.DevLong,
                            PyTango.AttrWriteType.READ, 4096)
                        self.add_attribute(
                            myAttr, LavueController.read_ROIsDynamicAttr,
                            None, None)
                        self.current_attributes.add(str(alias))
            self.current_rois_aliases = self.requested_rois_aliases

    def initialize_dynamic_attributes(self):
        self.debug_stream("In initialize_dynamic_attributes()")

        #   Example to add dynamic attributes
        #   Copy inside the folowing protected area to instanciate at startup.

        """   For Attribute ScalarDynamicAttr
        myScalarDynamicAttr = PyTango.Attr(
              'MyScalarDynamicAttr', PyTango.DevDouble, PyTango.READ)
        self.add_attribute(
              myScalarDynamicAttr,LavueController.read_ScalarDynamicAttr,
              None, None)
        self.attr_ScalarDynamicAttr_read = 0.0
        """

        """   For Attribute SpectrumDynamicAttr
        mySpectrumDynamicAttr = PyTango.SpectrumAttr(
              'MySpectrumDynamicAttr', PyTango.DevDouble, PyTango.READ, 4096)
        self.add_attribute(mySpectrumDynamicAttr,
              LavueController.read_SpectrumDynamicAttr, None, None)
        self.attr_SpectrumDynamicAttr_read = [0.0]
        """
        self.initialize_dynamic_rois_sums_attributes()
        self.initialize_dynamic_rois_attributes()

    def read_attr_hardware(self, data):
        self.debug_stream("In read_attr_hardware()")

    # -------------------------------------------------------------------------
    #    LavueController command methods
    # -------------------------------------------------------------------------

    def dev_state(self):
        """ This command gets the device state
        (stored in its device_state data member) and returns it to the caller.

        :return: Device state
        :rtype: PyTango.CmdArgType.DevState
        """
        self.debug_stream("In dev_state()")
        argout = PyTango.DevState.UNKNOWN
        argout = PyTango.DevState.ON
        return argout
        if argout != PyTango.DevState.ALARM:
            PyTango.Device_4Impl.dev_state(self)
        return self.get_state()

    def dev_status(self):
        """ This command gets the device status
            (stored in its device_status data member)
            and returns it to the caller.
        :return: Device status
        :rtype: PyTango.ConstDevString
        """
        self.debug_stream("In dev_status()")
        self.argout = "State is ON"

        self.set_status(self.argout)
        self.__status = PyTango.Device_4Impl.dev_status(self)
        return self.__status


class LavueControllerClass(PyTango.DeviceClass):
    # -------- Add you global class variables here --------------------------

    def dyn_attr(self, dev_list):
        """Invoked to create dynamic attributes for the given devices.
        Default implementation calls
        :meth:`LavueController.initialize_dynamic_attributes` for each device

        :param dev_list: list of devices
        :type dev_list: :class:`PyTango.DeviceImpl`"""

        for dev in dev_list:
            try:
                dev.initialize_dynamic_attributes()
            except:
                import traceback
                dev.warn_stream("Failed to initialize dynamic attributes")
                dev.debug_stream("Details: " + traceback.format_exc())

    #    Class Properties
    class_property_list = {
        }

    #    Device Properties
    device_property_list = {
        'DynamicROIs':
        [PyTango.DevBoolean,
         "create dynamically Attributes for ROIs aliases with their bounds",
         [True]],
        'DynamicROIsValues':
        [PyTango.DevBoolean,
         "create dynamically Attributes for ROIs aliases with their sums",
         [True]],
        'ROIAttributesNames':
        [PyTango.DevVarStringArray,
         "fixed ROI attributes names, They should ends with ROI, Sum or Sums",
         []],
    }

    #    Command definitions
    cmd_list = {
        }

    #    Attribute definitions
    attr_list = {
        'BeamCenterX':
            [[PyTango.DevDouble,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "beam center x",
                 'unit': "pixels",
                 'abs_change': "0.0001",
                 'Memorized': "true"
             }],
        'BeamCenterY':
            [[PyTango.DevDouble,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "beam center y",
                 'unit': "pixels",
                 'abs_change': "0.0001",
                 'Memorized': "true"
             }],
        'DetectorDistance':
            [[PyTango.DevDouble,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "detector distance",
                 'unit': "mm",
                'abs_change': "0.0001",
                 'Memorized': "true"
             }],
        'DetectorROIs':
            [[PyTango.DevString,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "detector ROIs",
                 'description': "json dictionary with detector ROIs, e.g.  "
                 "{\"pilatusroi1\": [[26, 15, 232, 65]], "
                 "\"lambdarois\": [[54, 78, 102, 204], [10, 20, 40, 50]]}",
                 'Display level': PyTango.DispLevel.EXPERT,
                 'Memorized': "true"
             }],
        'DetectorROIsValues':
            [[PyTango.DevString,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "detector ROIs values",
                 'description':
                 "json dictionary with detector ROIs values, e.g.  "
                 "{\"pilatusroi1\": [26.3], \"lambdarois\": [54.3, 434.30]]}",
                 'Memorized': "true"
             }],
        'DetectorROIsParams':
            [[PyTango.DevString,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "detector ROIs parameters",
                 'description': "json dictionary with detector ROIs "
                 "parameters, e.g.  {\"transpose\": False, "
                 "\"flip-lr\": True,  \"flip-ud\": False}",
                 'Memorized': "true"
             }],
        'Energy':
            [[PyTango.DevDouble,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "beam energy",
                 'unit': "eV",
                 'abs_change': "0.0001",
                 'Memorized': "true"
             }],
        'LavueState':
            [[PyTango.DevString,
              PyTango.SCALAR,
              PyTango.READ_WRITE],
             {
                 'label': "Lavue State",
                 'description': "json dictionary with Lavue State "
                 "configuration, "
                 "e.g. {\"source\": \"hidra\", "
                 "\"configuration\":\"hasodet.desy.de\"}",
                 'Display level': PyTango.DispLevel.EXPERT,
                 # 'Memorized': "true"
             }],
        }


def main():
    try:
        py = PyTango.Util(sys.argv)
        py.add_class(LavueControllerClass, LavueController, 'LavueController')

        U = PyTango.Util.instance()
        U.server_init()
        U.server_run()

    except PyTango.DevFailed as e:
        print('-------> Received a DevFailed exception: %s' % e)
    except Exception as e:
        print('-------> An unforeseen exception occured.... %s' % e)


if __name__ == '__main__':
    main()
