"""
CSP Subarray Leaf node monitors the CSP Subarray and issues control actions during an observation.
It also acts as a CSP contact point for Subarray Node for observation execution for TMC.
"""
# -*- coding: utf-8 -*-
#
# This file is part of the CspSubarrayLeafNode project
#
#
#
# Distributed under the terms of the BSD-3-Clause license.
# See LICENSE.txt for more info.

# PROTECTED REGION ID(CspSubarrayLeafNode.additional_import) ENABLED START #
# Standard python imports

import threading
import os

# PyTango imports
import tango
from tango import DebugIt, AttrWriteType, ApiUtil
from tango.server import run, attribute, command, device_property

# Additional import
from ska.base.commands import ResultCode
from ska.base import SKABaseDevice
from ska.base.control_model import ObsState

from tmc.common.tango_client import TangoClient
from tmc.common.tango_server_helper import TangoServerHelper

from .device_data import DeviceData
from .assign_resources_command import AssignResourcesCommand
from .release_all_resources_command import ReleaseAllResourcesCommand
from .configure_command import ConfigureCommand
from .scan_command import StartScanCommand
from .end_scan_command import EndScanCommand
from .end_command import GoToIdleCommand
from .abort_command import AbortCommand
from .restart_command import RestartCommand
from .obsreset_command import ObsResetCommand
from .reset_command import ResetCommand
from .telescope_on_command import TelescopeOn
from .telescope_off_command import TelescopeOff
from . import const, release
from .exceptions import InvalidObsStateError
from .delay_model import DelayManager

# PROTECTED REGION END #    //  CspSubarrayLeafNode.additional_import

__all__ = [
    "CspSubarrayLeafNode",
    "main",
    "AssignResourcesCommand",
    "ReleaseAllResourcesCommand",
    "ConfigureCommand",
    "StartScanCommand",
    "EndScanCommand",
    "GoToIdleCommand",
    "AbortCommand",
    "RestartCommand",
    "ObsResetCommand",
    "ResetCommand",
    "TelescopeOn",
    "TelescopeOff",
]


class CspSubarrayLeafNode(SKABaseDevice):
    """
    CSP Subarray Leaf node monitors the CSP Subarray and issues control actions during an observation.

    :Device Properties:

        CspSubarrayFQDN:
            Property to provide FQDN of CSP Subarray Device

    :Device Attributes:

        activityMessage:
            String providing information about the current activity in CspSubarrayLeaf Node.

        delayModel:
            Attribute to provide the delay model.

        versionInfo:
            Provides Version information of TANGO device.

    """

    # -----------------
    # Device Properties
    # -----------------
    CspSubarrayFQDN = device_property(
        dtype="str",
    )

    # ----------
    # Attributes
    # ----------
    delayModel = attribute(
        dtype="str",
        access=AttrWriteType.READ_WRITE,
    )

    versionInfo = attribute(
        dtype="str",
    )

    activityMessage = attribute(
        dtype="str",
        access=AttrWriteType.READ_WRITE,
    )

    # ---------------
    # General methods
    # ---------------

    class InitCommand(SKABaseDevice.InitCommand):
        """
        A class for the CspSubarrayLeafNode's init_device() method"
        """

        def do(self):
            """
            Initializes the attributes and properties of the CspSubarrayLeafNode.

            :return: A tuple containing a return code and a string message indicating status. The message is
            for information purpose only.

            :rtype: (ReturnCode, str)

            :raises: DevFailed if error occurs in creating proxy for CSPSubarray.
            """
            super().do()
            device = self.target

            this_server = TangoServerHelper.get_instance()
            this_server.set_tango_class(device)
            device.attr_map = {}
            device.attr_map["delayModel"] = " "
            device.attr_map["activityMessage"] = ""

            device._build_state = "{},{},{}".format(
                release.name, release.version, release.description
            )
            device._version_id = release.version
            device._versioninfo = " "
            standalone_mode = os.environ.get("STANDALONE_MODE")
            self.logger.info("Device running in standalone_mode:%s", standalone_mode)
            # The IERS_A file needs to be downloaded each time when the MVP is deployed.
            delay_manager_obj = DelayManager.get_instance()
            try:
                download_iers_thread = threading.Thread(
                    None, delay_manager_obj.download_IERS_file, "CspSubarrayLeafNode"
                )
                download_iers_thread.start()
            except Exception as delay_execption:
                log_msg = f"Exception in DelayCorrection Katpoint API {delay_execption}"
                self.logger.exception(log_msg)
                tango.Except.throw_exception(
                    const.STR_CMD_FAILED,
                    log_msg,
                    "CspSubarrayLeafNode.InitCommand.do()",
                    tango.ErrSeverity.ERR,
                )

            ApiUtil.instance().set_asynch_cb_sub_model(tango.cb_sub_model.PUSH_CALLBACK)
            this_server.write_attr(
                "activityMessage",
                f"{const.STR_SETTING_CB_MODEL}{ApiUtil.instance().get_asynch_cb_sub_model()}",
                False,
            )
            this_server.set_status(const.STR_CSPSALN_INIT_SUCCESS)
            this_server.write_attr(
                "activityMessage", const.STR_CSPSALN_INIT_SUCCESS, False
            )
            self.logger.info(const.STR_CSPSALN_INIT_SUCCESS)
            return (ResultCode.OK, const.STR_CSPSALN_INIT_SUCCESS)

    def always_executed_hook(self):
        # PROTECTED REGION ID(CspSubarrayLeafNode.always_executed_hook) ENABLED START #
        """Internal construct of TANGO."""
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.always_executed_hook

    def delete_device(self):
        # PROTECTED REGION ID(CspSubarrayLeafNode.delete_device) ENABLED START #
        """Internal construct of TANGO."""
        self.logger.debug("Exiting.")
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.delete_device

    # ------------------
    # Attributes methods
    # ------------------
    def read_delayModel(self):
        # PROTECTED REGION ID(CspSubarrayLeafNode.delayModel_read) ENABLED START #
        """Internal construct of TANGO. Returns the delay model."""
        return self.attr_map["delayModel"]
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.delayModel_read

    def write_delayModel(self, value):
        # PROTECTED REGION ID(CspSubarrayLeafNode.delayModel_write) ENABLED START #
        """Internal construct of TANGO. Sets in to the delay model."""
        self.update_attr_map("delayModel", value)
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.delayModel_write

    def read_versionInfo(self):
        # PROTECTED REGION ID(CspSubarrayLeafNode.versionInfo_read) ENABLED START #
        """Internal construct of TANGO. Returns the version information."""
        return self._versioninfo
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.versionInfo_read

    def read_activityMessage(self):
        # PROTECTED REGION ID(CspSubarrayLeafNode.activityMessage_read) ENABLED START #
        """Internal construct of TANGO. Returns activity message."""
        return self.attr_map["activityMessage"]
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.activityMessage_read

    def write_activityMessage(self, value):
        # PROTECTED REGION ID(CspSubarrayLeafNode.activityMessage_write) ENABLED START #
        """Internal construct of TANGO. Sets the activity message."""
        self.update_attr_map("activityMessage", value)
        # PROTECTED REGION END #    //  CspSubarrayLeafNode.activityMessage_write

    def update_attr_map(self, attr, val):
        """
        This method updates attribute value in attribute map. Once a thread has acquired a lock,
        subsequent attempts to acquire it are blocked, until it is released.
        """
        lock = threading.Lock()
        lock.acquire()
        self.attr_map[attr] = val
        lock.release()

    # --------
    # Commands
    # --------

    @command()
    @DebugIt()
    def TelescopeOn(self):
        """Invokes TelescopeOn command on cspsubarrayleafnode"""
        handler = self.get_command_object("TelescopeOn")
        handler()

    def is_TelescopeOn_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
                 current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
                 in current device state
        """
        handler = self.get_command_object("TelescopeOff")
        return handler.check_allowed()

    @command()
    @DebugIt()
    def TelescopeOff(self):
        """Invokes TelescopeOff command on cspsubarrayleafnode"""
        handler = self.get_command_object("TelescopeOff")
        handler()

    def is_TelescopeOff_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
                 current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
                 in current device state
        """
        handler = self.get_command_object("TelescopeOff")
        return handler.check_allowed()

    def is_Configure_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
        current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
        in current device state

        """
        handler = self.get_command_object("Configure")
        return handler.check_allowed()

    @command(
        dtype_in=("str"),
        doc_in="The string in JSON format, contains CSP configuration id, frequencyBand, fsp,"
        " delayModelSubscriptionPoint and pointing information.",
    )
    @DebugIt()
    def Configure(self, argin):
        """Invokes Configure command on CspSubarrayLeafNode"""
        handler = self.get_command_object("Configure")
        handler(argin)

    @command(
        dtype_in=("str",),
        doc_in="The string in JSON format, consists of scan id.",
    )
    @DebugIt()
    def StartScan(self, argin):
        """Invokes StartScan command on cspsubarrayleafnode"""
        handler = self.get_command_object("StartScan")
        handler(argin)

    def is_StartScan_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
        current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
        in current device state

        """
        handler = self.get_command_object("StartScan")
        return handler.check_allowed()

    def is_EndScan_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run in current device state

        """
        handler = self.get_command_object("EndScan")
        return handler.check_allowed()

    @command()
    @DebugIt()
    def EndScan(self):
        """Invokes EndScan command on CspSubarrayLeafNode"""
        handler = self.get_command_object("EndScan")
        handler()

    def is_ReleaseAllResources_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
        current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
        in current device state

        """
        handler = self.get_command_object("ReleaseAllResources")
        return handler.check_allowed()

    @command()
    @DebugIt()
    def ReleaseAllResources(self):
        """Invokes ReleaseAllResources command on CspSubarrayLeafNode"""
        handler = self.get_command_object("ReleaseAllResources")
        handler()

    def is_AssignResources_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run in current device state

        """
        handler = self.get_command_object("AssignResources")
        return handler.check_allowed()

    @command(
        dtype_in=("str"),
        doc_in="The input string in JSON format consists of receptorIDList.",
    )
    @DebugIt()
    def AssignResources(self, argin):
        """Invokes AssignResources command on CspSubarrayLeafNode."""
        handler = self.get_command_object("AssignResources")
        try:
            self.validate_obs_state()

        except InvalidObsStateError as error:
            self.logger.exception(error)
            tango.Except.throw_exception(
                const.ERR_DEVICE_NOT_EMPTY_OR_IDLE,
                "CSP subarray leaf node raised exception",
                "CSP.AddReceptors",
                tango.ErrSeverity.ERR,
            )
        handler(argin)

    def is_GoToIdle_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
        current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
        in current device state

        """
        handler = self.get_command_object("GoToIdle")
        return handler.check_allowed()

    @command()
    @DebugIt()
    def GoToIdle(self):
        """Invokes GoToIdle command on CspSubarrayLeafNode."""
        handler = self.get_command_object("GoToIdle")
        handler()

    def validate_obs_state(self):
        this_server = TangoServerHelper.get_instance()
        csp_subarray_fqdn = this_server.read_property("CspSubarrayFQDN")[0]
        csp_sa_client = TangoClient(csp_subarray_fqdn)
        if csp_sa_client.get_attribute("obsState").value in [
            ObsState.EMPTY,
            ObsState.IDLE,
        ]:
            self.logger.info(
                "CSP Subarray is in required obsState, resources will be assigned"
            )
        else:
            self.logger.error("CSP Subarray is not in EMPTY/IDLE obsState")
            this_server.write_attr("activityMessage", "Error in device obsState", False)
            raise InvalidObsStateError("CSP Subarray is not in EMPTY/IDLE obsState")

    @command()
    @DebugIt()
    def Abort(self):
        """Invokes Abort command on CspSubarrayLeafNode"""
        handler = self.get_command_object("Abort")
        handler()

    def is_Abort_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
                 current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
                 in current device state
        """
        handler = self.get_command_object("Abort")
        return handler.check_allowed()

    @command()
    @DebugIt()
    def Restart(self):
        """Invokes Restart command on cspsubarrayleafnode"""
        handler = self.get_command_object("Restart")
        handler()

    def is_Restart_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
                 current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
                 in current device state
        """
        handler = self.get_command_object("Restart")
        return handler.check_allowed()

    @command()
    @DebugIt()
    def ObsReset(self):
        """Invokes ObsReset command on cspsubarrayleafnode"""
        handler = self.get_command_object("ObsReset")
        handler()

    def is_ObsReset_allowed(self):
        """
        Checks whether the command is allowed to be run in the current state

        :return: True if this command is allowed to be run in
                 current device state

        :rtype: boolean

        :raises: DevFailed if this command is not allowed to be run
                 in current device state
        """
        handler = self.get_command_object("ObsReset")
        return handler.check_allowed()

    def init_command_objects(self):
        """
        Initialises the command handlers for commands supported by this
        device.
        """
        super().init_command_objects()
        device_data = DeviceData.get_instance()
        args = (device_data, self.state_model, self.logger)
        self.register_command_object("AssignResources", AssignResourcesCommand(*args))
        self.register_command_object(
            "ReleaseAllResources", ReleaseAllResourcesCommand(*args)
        )
        self.register_command_object("Configure", ConfigureCommand(*args))
        self.register_command_object("StartScan", StartScanCommand(*args))
        self.register_command_object("EndScan", EndScanCommand(*args))
        self.register_command_object("GoToIdle", GoToIdleCommand(*args))
        self.register_command_object("Abort", AbortCommand(*args))
        self.register_command_object("Restart", RestartCommand(*args))
        self.register_command_object("ObsReset", ObsResetCommand(*args))
        self.register_command_object("Reset", ResetCommand(*args))
        self.register_command_object("TelescopeOff", TelescopeOff(*args))
        self.register_command_object("TelescopeOn", TelescopeOn(*args))


# ----------
# Run server
# ----------


def main(args=None, **kwargs):
    # PROTECTED REGION ID(CspSubarrayLeafNode.main) ENABLED START #
    """
    Runs the CspSubarrayLeafNode.

    :param args: Arguments internal to TANGO
    :param kwargs: Arguments internal to TANGO

    :return: CspSubarrayLeafNode TANGO object.

    """
    ret_val = run((CspSubarrayLeafNode,), args=args, **kwargs)

    return ret_val

    # PROTECTED REGION END #    //  CspSubarrayLeafNode.main


if __name__ == "__main__":
    main()
