# ----------------------------------------------------------------------------
# Description    : SCPI interface
# Git repository : https://gitlab.com/qblox/packages/software/qblox_instruments.git
# Copyright (C) Qblox BV (2020)
# ----------------------------------------------------------------------------


# ----------------------------------------------------------------------------
# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY THIS FILE MANUALLY!
# ----------------------------------------------------------------------------


# -- include ------------------------------------------------------------------

import sys
import struct
import re
import json
from typing import Any, Dict, List, Optional, Tuple

# Add IEEE488.2 support
from qblox_instruments.ieee488_2 import Ieee488_2, Transport
from qblox_instruments.scpi.error_check import scpi_error_check
from qblox_instruments.types import DebugLevel


# -- class --------------------------------------------------------------------


class CfgMan(Ieee488_2):
    """
    This interface provides an API for the mandatory and required SCPI calls and adds
    Cluster related functionality
    (see `SCPI <https://www.ivifoundation.org/downloads/SCPI/scpi-99.pdf>`_).
    """

    # -------------------------------------------------------------------------
    def __init__(
        self, transport: Transport, debug: DebugLevel = DebugLevel.MINIMAL_CHECK
    ):
        """
        Creates SCPI interface object.

        Parameters
        ----------
        transport : Transport
            Transport class responsible for the lowest level of communication
            (e.g. Ethernet).
        debug : DebugLevel
            Debug level, which determines if and when version match checks and
            SCPI error checks are done.

        Returns
        ----------

        Raises
        ----------
        ConnectionError
            version_check is True and there is a device or version mismatch.
        """
        self._debug = debug

        # Initialize parent class.
        super(CfgMan, self).__init__(transport)

        if self._debug != DebugLevel.NO_CHECK:
            # Clear SCPI error queue.
            while int(self._read("SYSTem:ERRor:COUNt?")) != 0:
                self._read("SYSTem:ERRor:NEXT?")

    # -------------------------------------------------------------------------
    def get_system_error(self) -> str:
        """
        Get system error from queue (see `SCPI <https://www.ivifoundation.org/downloads/SCPI/scpi-99.pdf>`_).

        Parameters
        ----------
        None

        Returns
        -------
        str
            System error description string.

        """

        # SCPI call
        var0 = self._read("SYSTem:ERRor:NEXT?")

        return var0

    # -------------------------------------------------------------------------
    def get_num_system_error(self) -> int:
        """
        Get number of system errors (see `SCPI <https://www.ivifoundation.org/downloads/SCPI/scpi-99.pdf>`_).

        Parameters
        ----------
        None

        Returns
        -------
        int
            Current number of system errors.

        """

        # SCPI call
        var0 = self._read("SYSTem:ERRor:COUNt?")

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _get_idn(self) -> str:
        """
        Get device identity and build information.

        Parameters
        ----------
        None

        Returns
        -------
        str
            Concatenated list of strings separated by the semicolon character. The IDN consists of four strings respectively ordered as:

            - Manufacturer
            - Model
            - Serial number
            - Build information

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("*IDN?")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def get_json_description(self) -> Any:
        """
        Returns a JSON description of the instrument and all its managed modules.

        Parameters
        ----------
        None

        Returns
        -------
        Any
            Instrument description structure.

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read_bin("*DESCribe?")

        return json.loads(var0.decode("utf-8"))

    # -------------------------------------------------------------------------
    @scpi_error_check
    def get_system_version(self) -> str:
        """
        Get SCPI system version (see `SCPI <https://www.ivifoundation.org/downloads/SCPI/scpi-99.pdf>`_).

        Parameters
        ----------
        None

        Returns
        -------
        str
            SCPI system version.

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("SYSTem:VERSion?")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _get_scpi_commands(self) -> str:
        """
        Get SCPI commands.

        Parameters
        ----------
        None

        Returns
        -------
        str
            Concatenated list of strings separated by the semicolon character. Each command consists of nine sections respectively ordered as:

             - SCPI command pattern
             - SCPI input type
             - SCPI output type
             - Python function
             - Python input types (comma separated)
             - Python input variable names (comma separated)
             - Python output types (comma separated)
             - User access level (0 = public, >=1 = private)
             - Comment


        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("*CMDS?")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _temp_file_new(self) -> int:
        """
        Creates a temporary file for the purpose of sending a large batch of data to the instrument, such as an update file.

        Parameters
        ----------
        None

        Returns
        -------
        int
            Handle by which the file can be identified.

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("TMPFILe:NEW?")

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _temp_file_delete(self, handle: int) -> None:
        """
        Deletes a temporary file.

        Parameters
        ----------
        handle : int
            Temporary file handle.

        Returns
        -------
        None

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int"])

        # SCPI call
        self._write(f"TMPFILe:DELete {handle}")

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _temp_file_size(self, handle: int) -> int:
        """
        Returns the size of a temporary file in bytes.

        Parameters
        ----------
        handle : int
            Temporary file handle.

        Returns
        -------
        int
            Size of the file in bytes.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int"])

        # SCPI call
        var0 = self._read(f"TMPFILe:SIZe? {handle}")

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _temp_file_block_count(self, handle: int) -> int:
        """
        Returns the number of blocks that can be read from a temporary file.

        Parameters
        ----------
        handle : int
            Temporary file handle.

        Returns
        -------
        int
            Number of blocks that can be read.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int"])

        # SCPI call
        var0 = self._read(f"TMPFILe:BLOCK:COUNT? {handle}")

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _temp_file_block_read(self, handle: int, block: int) -> bytes:
        """
        Reads the nth block of a temporary file.

        Parameters
        ----------
        handle : int
            Temporary file handle.
        block : int
            Block index.

        Returns
        -------
        bytes
            Contents of the block.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int", "int"])

        # SCPI call
        var0 = self._read_bin(f"TMPFILe:BLOCK:READ? {handle},{block}")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _temp_file_append(self, handle: int, data: bytes) -> None:
        """
        Appends a block of data to a temporary file.

        Parameters
        ----------
        handle : int
            Temporary file handle.
        data : bytes
            Data to append.

        Returns
        -------
        None

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int", "bytes"])

        # SCPI call
        self._write_bin(f"TMPFILe:APPend {handle},", data)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _task_poll(self, handle: int) -> float:
        """
        Polls for completion of the given task.

        Parameters
        ----------
        handle : int
            Task handle.

        Returns
        -------
        float
            Task progress from 0.0 to 1.0. 1.0 indicates completion.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int"])

        # SCPI call
        var0 = self._read(f"TASK:POLL? {handle}")

        return float(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _task_get_result(self, handle: int) -> Any:
        """
        Retrieves the value returned by a task. The task must be complete. If the task yielded an error, the error will be returned by this call. The task handle is deleted once the value is returned.

        Parameters
        ----------
        handle : int
            Task handle.

        Returns
        -------
        Any
            The result of the task.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int"])

        # SCPI call
        var0 = self._read_bin(f"TASK:RESult? {handle}")

        return json.loads(var0.decode("utf-8"))

    # -------------------------------------------------------------------------
    @scpi_error_check
    def set_name(self, name: str) -> None:
        """
        Sets the customer-specified name of the instrument. The name must not contain any newlines, backslashes, or double quotes.

        Parameters
        ----------
        name : str
            The new name for the device.

        Returns
        -------
        None

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["str"])

        # SCPI call
        self._write(f'SYSTem:NAME "{name}"')

    # -------------------------------------------------------------------------
    @scpi_error_check
    def get_name(self) -> str:
        """
        Returns the customer-specified name of the instrument.

        Parameters
        ----------
        None

        Returns
        -------
        str
            The name of the device.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("SYSTem:NAME?")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _download_log(self, source: str, format: int) -> int:
        """
        Prepares a batch of log data for retrieval via temp_file_*(). This may take some time, so instead of returning immediately, this spawns a task. task_poll() may be used to poll the state of the task, once this returns True (task complete), use task_get_result() to get the temp_file handle.

        Parameters
        ----------
        source : str
            The log source. Must be "system" for the Linux system log,"cfg_man" for the configuration manager log, or "app" for the main application log.
        format : int
            The format. Specify a positive integer to get up to that number of lines from the log. Specify zero to get the complete current log file (older log entries may exist via logrotate, only the file currently being written is returned). Specify a negative number to get all available log data in .tgz format.

        Returns
        -------
        int
            Task handle.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["str", "int"])

        # SCPI call
        var0 = self._read(f'SYSTem:LOG? "{source}",{format}')

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def set_ip_config(self, config: str) -> None:
        """
        Reconfigures the IP address of this device. The configuration will not go into effect until reboot() is called or the device is power-cycled.

        Parameters
        ----------
        config : str
            IP configuration. May be one of the following things:
             - an IPv4 address including prefix length, for example 192.168.0.2/24, - the string `dhcp` to enable IPv4 DHCP, - an IPv6 address including prefix length, for example 1:2::3:4/64, or - a semicolon-separated combination of an IPv4 configuration (IP address or `dhcp`) and an IPv6 address.

        Returns
        -------
        None

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["str"])

        # SCPI call
        self._write(f'SYSTem:IPCONFig "{config}"')

    # -------------------------------------------------------------------------
    @scpi_error_check
    def get_ip_config(self) -> str:
        """
        Returns the IP address configuration that will go into effect when the device reboots.

        Parameters
        ----------
        None

        Returns
        -------
        str
            IP configuration. Can be one of the following things:
             - an IPv4 address including prefix length, for example 192.168.0.2/24, - the string `dhcp` to enable IPv4 DHCP, - an IPv6 address including prefix length, for example 1:2::3:4/64, or - a semicolon-separated combination of an IPv4 configuration (IP address or `dhcp`) and an IPv6 address.

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("SYSTem:IPCONFig?")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _get_update_module_types(self) -> str:
        """
        Returns a comma-separated list of device types that may be updated in addition to our own device type in the form of cluster modules. Returns ``not_applicable`` if this is not applicable to this device type.

        Parameters
        ----------
        None

        Returns
        -------
        str
            Comma-separated list of device types.

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("SYSTem:UPDATE:MODules?")

        return var0

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _exclude_from_update(self, excluded_slots: Any) -> None:
        """
        Exclude the specified slots from the update

        Parameters
        ----------
        excluded_slots : Any
            List of slots to exclude from update

        Returns
        -------
        None

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["Any"])

        # SCPI call
        self._write_bin(
            f"SYSTem:UPDATE:EXCLude", json.dumps(excluded_slots).encode("utf-8")
        )

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _include_in_update(self, excluded_slots: Any) -> None:
        """
        Include only the specified slots in the update

        Parameters
        ----------
        excluded_slots : Any
            List of slots to update

        Returns
        -------
        None

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["Any"])

        # SCPI call
        self._write_bin(
            f"SYSTem:UPDATE:INCLude", json.dumps(excluded_slots).encode("utf-8")
        )

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _update(self, handle: int) -> int:
        """
        Updates the device with an update tarball that was previously uploaded using temp_file_*() commands. This command merely starts the update process, and returns a task handle that can polled for completion via task_poll(), at which point any errors that may have occurred must be retrieved using task_get_result(). The task will return the string "OK" if successful. If this happens, the device must be rebooted via the reboot() command or via a power cycle to fully apply the update. Do not power down the device while the update task is running.

        Parameters
        ----------
        handle : int
            Temporary file handle for the update tarball.

        Returns
        -------
        int
            Task handle.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # Check input types.
        self._check_in_type(locals(), ["int"])

        # SCPI call
        var0 = self._read(f"SYSTem:UPDATE:PREPare? {handle}")

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def _rollback(self) -> int:
        """
        Rolls back the device to the previous version, if rollback information is available. This command merely starts the rollback process, and returns a task handle that can polled for completion via task_poll(), at which point any errors that may have occurred must be retrieved using task_get_result(). The task will return the string "OK" if successful. If this happens, the device must be rebooted via the reboot() command or via a power cycle to fully apply the rollback. Do not power down the device while the rollback task is running.

        Parameters
        ----------
        None

        Returns
        -------
        int
            Task handle.

        Raises
        ------
        Exception
            Invalid input parameter type.
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        var0 = self._read("SYSTem:ROLLBACK:PREPare?")

        return int(var0)

    # -------------------------------------------------------------------------
    @scpi_error_check
    def reboot(self) -> None:
        """
        Reboots the instrument.

        Parameters
        ----------
        None

        Returns
        -------
        None

        Raises
        ------
        Exception
            An error is reported in system error and debug <= 1.
            All errors are read from system error and listed in the exception.
        """

        # SCPI call
        self._write("SYSTem:REBOOT")

    # ---------------------------------------------------------------------
    def _check_in_type(self, in_arg_dict: Dict, in_type_list: List) -> None:
        """
        Checks input argument types against reference types.

        Parameters
        ----------
        in_arg_dict : dict
            Dictionary with input arguments created by locals().
        in_type_list : list
            List of reference input argument types.

        Returns
        ----------

        Raises
        ----------
        TypeError
            Input argument type mismatch.
        """

        if self._debug != DebugLevel.NO_CHECK:
            del in_arg_dict["self"]
            in_val_list = [in_arg_dict[name] for name in in_arg_dict]
            for i, (in_val, in_type) in enumerate(zip(in_val_list, in_type_list)):
                if (
                    type(in_val).__name__ == "list"
                    or type(in_val).__name__ == "ndarray"
                ):
                    if len(in_val) > 0:
                        in_val = in_val[0]
                    else:
                        raise TypeError(
                            f"Unexpected type for input argument {i}, expected {in_type} but got empty {str(type(in_val).__name__)}."
                        )
                if in_type == "Any":
                    continue
                if type(in_val).__name__[: len(in_type)] != in_type:
                    raise TypeError(
                        f"Unexpected type for input argument {i}, expected {in_type} but got {str(type(in_val).__name__)}."
                    )

    # -------------------------------------------------------------------------
    def check_error_queue(self, err: Optional[Exception] = None) -> None:
        """
        Check system error for errors. Empties and prints the complete error
        queue.

        Parameters
        ----------
        err : Optional[Exception]
            Exception to reraise.

        Returns
        ----------

        Raises
        ----------
        Exception
            An exception was passed as input argument.
        RuntimeError
            An error was found in system error.
        """

        # ExceptionGroup is Python 3.11+, this snippet can be removed when 3.10 support is dropped
        # (And Exception below replaced with `ExceptionGroup`
        try:
            GroupException = ExceptionGroup
        except NameError:
            # Somehow 3.11 still detects the class below when it is named ExceptionGroup
            class GroupException(Exception):
                def __init__(self, message: str, exceptions: list[Exception]):
                    self.exceptions = exceptions
                    message = f"{message}: {[f'{type(e).__name__}: {e!s}' for e in exceptions]}"
                    super().__init__(message)

        errors: list[Exception] = [err] if err is not None else []
        while int(self._read("SYSTem:ERRor:COUNt?")) != 0:
            errors.append(
                RuntimeError(",".join(self._read("SYSTem:ERRor:NEXT?").split(",")[1:]))
            )

        if len(errors) == 1:
            raise errors[0].with_traceback(sys.exc_info()[2]) from errors[0]

        if len(errors) > 1:
            raise GroupException("Multiple errors occurred", errors).with_traceback(
                sys.exc_info()[2]
            )
