#!/usr/bin/env python
###############################################################
# \package CyberRadioDriver.components
#
# \brief Provides classes that define basic functionality for
#     hardware components (tuners, wideband DDCs, narrowband
#     DDCs, etc.).
#
# \note This module defines basic behavior only.  To customize
#     a component class for a particular radio, derive a new
#     class from the appropriate base class.  It is recommended
#     that behavior specific to a given radio be placed in the
#     module that supports that radio.
#
# \author NH
# \author DA
# \author MN
# \copyright Copyright (c) 2014-2021 CyberRadio Solutions, Inc.
#    All rights reserved.
#
###############################################################

# Imports from other modules in this package
from . import command
from . import configKeys
from . import log
# Imports from external modules
import numpy
# Python standard library imports
# import time
# import traceback

##
# \internal
# Helper method that adjusts an input frequency to a given
# range, resolution, and unit specifier.
def adjustFrequency(frqIn, frange, res, units):
    frqOut = frange[0] if frqIn<frange[0] else frqIn
    frqOut = frange[1] if frqIn>frange[1] else frqOut
    frqOut = numpy.around( frqOut, int(numpy.log10(1.0/res)), )
    frqOut /= units
    return frqOut

##
# \internal
# Helper method that adjusts an input attenuation to a given
# range, resolution, and unit specifier.
def adjustAttenuation(attIn, attRange, attRes, attUnits=1):
    attOut = max(attIn, attRange[0])
    attOut = min(attOut, attRange[1])
    attOut = numpy.around( attOut, int(numpy.log10(1.0/attRes)), )
    attOut /= attUnits
    return attOut

class DDC_DATA_FORMAT():
    IQ = "iq"
    REAL = "real"
    FFT = "fft"

#----------------------------------------------------------------------------#
#--  Base Radio Component Object  -------------------------------------------#

##
# Base hardware component class.
#
# A radio handler object maintains a series of component objects, one
# per component of each type (tuner, WBDDC, NBDDC, etc.).  Each component
# object is responsible for managing the hardware object that it represents.
# Each component object is also responsible for querying the component's
# current configuration and for maintaining the object's configuration
# as it changes during radio operation.
#
class _base(log._logger, configKeys.Configurable):
    _name = "_baseObject"
    ## Communication transport
    transport = None
    ## Callback function that executes commands on the transport
    callback = None
    ## Index
    index = None
    ##
    # Constructs a hardware component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self, *args, **kwargs):
        # Set up configuration capabilities
        configKeys.Configurable.__init__(self)
        # Consume keyword arguments "verbose" and "logFile" for logging support
        log._logger.__init__(self, *args, **kwargs)
        # Consume our own keyword arguments
        self.parent = kwargs.get("parent",None)
        self.callback = kwargs.get("callback",None)
        self.index = int( kwargs.get(configKeys.INDEX) )
        self.logIfVerbose("Hello!")

    ##
    # Adds a communication transport to this component.
    #
    # The component will query the hardware for its configuration after the
    # transport is added.
    #
    # \param transport The communication transport (an object of type
    #    CyberRadioDriver.transport.radio_transport).
    # \param callback A method that the component uses to send data over the
    #   connected transport.
    # \param queryConfig Whether or not to query the configuration
    #   through hardware commands
    def addTransport(self,transport,callback,queryConfig=True):
        self.transport = transport
        self.callback = callback
        if queryConfig:
            self.queryConfiguration()

    ##
    # Deletes the communication transport from this component.
    #
    def delTransport(self):
        self.transport = None
        self.callback = None

    ##
    # \internal
    # Perform actions on object being deleted.
    def __del__(self,):
        self.logIfVerbose("Goodbye!")

    ##
    # \internal
    # Define this object's string representation.
    def __str__(self,):
        return "%s.%s.%d" % (str(self.parent), self._name, self.index)

    ##
    # Disables this component.
    #
    def disable(self):
        return self._setConfiguration({configKeys.ENABLE: 0})

    ##
    # Enables this component.
    #
    def enable(self):
        return self._setConfiguration({configKeys.ENABLE: 1})

#----------------------------------------------------------------------------#
#--  Tuner Objects  ---------------------------------------------------------#

##
# Base tuner component class.
#
# A radio handler object maintains one tuner component object per tuner
# on the radio.
#
class _tuner(_base):
    _name = "Tuner"
    ## Tunable frequency range.  This is a 2-tuple: (minimum, maximum).
    frqRange = (20e6,3e9)
    ## Frequency resolution.
    frqRes = 1e6
    ## Frequency units.
    frqUnits = 1e6
    ## Tunable attenuation range.  This is a 2-tuple: (minimum, maximum).
    attRange = (0.0,30.0)
    ## Attenuation resolution.
    attRes = 1.0
    ## ifFilters
    ifFilters = [0, 1]
    ## Automatic gain control (AGC) supported.
    agc = False
    # Supported commands
    ## Frequency set/query command.
    frqCmd = command.frq
    ## Attenuation set/query command.
    attCmd = command.att
    ## AGP set/query command.
    agpCmd = None
    ## Tuner power set/query command.
    tpwrCmd = command.tpwr
    ## Tuner filter set/query command.
    fifCmd = None
    ## Timing adjustment set/query command.
    tadjCmd = None
    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                  configKeys.TUNER_FREQUENCY,
                                  configKeys.TUNER_ATTENUATION,
                                  configKeys.TUNER_RF_ATTENUATION,
                                  configKeys.ENABLE,
                                  ]

    ##
    # Constructs a tuner component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self,*args,**kwargs)

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.tpwrCmd is not None:
            cmd = self.tpwrCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.ENABLE] = rspInfo.get(configKeys.ENABLE, 0)
        if self.frqCmd is not None:
            cmd = self.frqCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                freq = rspInfo.get(configKeys.TUNER_FREQUENCY, None)
                self.configuration[configKeys.TUNER_FREQUENCY] = None if freq is None else \
                                                  freq * self.frqUnits
        if self.attCmd is not None:
            cmd = self.attCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.TUNER_ATTENUATION] = rspInfo.get(configKeys.TUNER_ATTENUATION, None)
                self.configuration[configKeys.TUNER_RF_ATTENUATION] = rspInfo.get(configKeys.TUNER_ATTENUATION, None)
        if self.agpCmd is not None:
            cmd = self.agpCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.TUNER_AGP] = rspInfo.get(configKeys.TUNER_AGP, None)
        if self.fifCmd is not None:
            cmd = self.fifCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.TUNER_FILTER] = rspInfo.get(configKeys.TUNER_FILTER, None)
        if self.tadjCmd is not None:
            cmd = self.tadjCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.TUNER_TIMING_ADJ] = rspInfo.get(configKeys.TUNER_TIMING_ADJ, None)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if configKeys.ENABLE in confDict:
            if self.tpwrCmd is not None:
                cmd = self.tpwrCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.ENABLE: confDict.get(configKeys.ENABLE, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.ENABLE] = getattr(cmd, configKeys.ENABLE)
                pass
        if configKeys.TUNER_FREQUENCY in confDict:
            if self.frqCmd is not None:
                freqIn = float(confDict.get(configKeys.TUNER_FREQUENCY, 0))
                freqAdj = adjustFrequency(freqIn, self.frqRange,
                                          self.frqRes, self.frqUnits)
                cmd = self.frqCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.TUNER_FREQUENCY: freqAdj,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TUNER_FREQUENCY] = freqAdj * self.frqUnits
                pass
        if configKeys.TUNER_ATTENUATION in confDict or configKeys.TUNER_RF_ATTENUATION in confDict:
            if self.attCmd is not None:
                rfAttIn = float(confDict.get(configKeys.TUNER_RF_ATTENUATION,
                                confDict.get(configKeys.TUNER_ATTENUATION, 0)))
                rfAttAdj = adjustAttenuation(rfAttIn, self.attRange,
                                            self.attRes, 1)
                cmd = self.attCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.TUNER_ATTENUATION: rfAttAdj,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TUNER_ATTENUATION] = rfAttAdj
                    self.configuration[configKeys.TUNER_RF_ATTENUATION] = rfAttAdj
                pass
        if configKeys.TUNER_AGP in confDict:
            if self.agpCmd is not None:
                cmd = self.agpCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.TUNER_AGP: confDict.get(configKeys.TUNER_AGP, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TUNER_AGP] = getattr(cmd, configKeys.TUNER_AGP)
                pass
        if configKeys.TUNER_FILTER in confDict:
            if self.fifCmd is not None:
                cmd = self.fifCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.TUNER_FILTER: confDict.get(configKeys.TUNER_FILTER, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TUNER_FILTER] = getattr(cmd, configKeys.TUNER_FILTER)
                pass
        if configKeys.TUNER_TIMING_ADJ in confDict:
            if self.tadjCmd is not None:
                cmd = self.tadjCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.TUNER_TIMING_ADJ: confDict.get(configKeys.TUNER_TIMING_ADJ, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TUNER_TIMING_ADJ] = getattr(cmd, configKeys.TUNER_TIMING_ADJ)
                pass
        return ret

#----------------------------------------------------------------------------#
#--  DDC Objects  -----------------------------------------------------------#

##
# Base DDC component class.
#
# A radio handler object maintains one DDC component object per DDC
# on the radio.
#
class _ddc(_base):
    _name = "DDC"
    ## Whether this is a wideband DDC.
    wideband = True
    ## Whether this DDC is tunable.
    tunable = False
    ## Whether this DDC can be set to a specific source tuner.
    selectableSource = False
    ## Whether this DDC supports automatic gain control (AGC).
    agc = False
    ## Tunable frequency offset range.  This is a 2-tuple: (minimum, maximum).
    frqRange = (-0.0,0.0)
    ## Tunable frequency offset resolution.
    frqRes = 1.0
    ## Tunable frequency offset units.
    frqUnits = 1.0
    ## DDC rate set.  This is a dictionary whose keys are rate index numbers and
    # whose values are DDC rates.
    rateSet = {}
    bwSet = {}
    dataFormat = {}
    ## DDC configuration query/set command.
    cfgCmd = None
    ## DDC tuning query/set command.    ## Whether this DDC is tunable.
    tunable = False
    ## Whether this DDC can be set to a specific source tuner.
    selectableSource = False
    ## Whether this DDC supports automatic gain control (AGC).
    agc = False
    ## Tunable frequency offset range.  This is a 2-tuple: (minimum, maximum).
    frqRange = (-0.0,0.0)
    ## Tunable frequency offset resolution.
    frqRes = 1.0
    ## Tunable frequency offset units.
    frqUnits = 1.0
    ## DDC rate set.  This is a dictionary whose keys are rate index numbers and
    # whose values are DDC rates.
    rateSet = {}

    frqCmd = None
    ## DDC source select query/set command.
    nbssCmd = None
    ## DDC data port (10GigE interface) query/set command.
    dportCmd = None
    ## DDC demod query/set command.
    demodCmd = None
    otherCmdList = []
    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                  configKeys.ENABLE,
                                  configKeys.DDC_RATE_INDEX,
                                  configKeys.DDC_UDP_DESTINATION,
                                  configKeys.DDC_VITA_ENABLE,
                                  configKeys.DDC_STREAM_ID,
                                  ]

    ##
    # Constructs a DDC component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self,*args,**kwargs)

    # EXTENSION
    ##
    # Gets the list of available DDC rates.
    #
    # \param index If not None, get the list for the DDC with the given index.
    # \return A list of DDC rates.
    @classmethod
    def getDdcRates(cls,index=None):
        rateKeys = list(cls.getDdcRateSet(index).keys())
        rateKeys.sort()
        return [cls.rateSet[k] for k in rateKeys]

    # EXTENSION
    ##
    # Gets the list of available DDC rates.
    #
    # \param index If not None, get the list for the DDC with the given index.
    # \return A list of DDC rates.
    @classmethod
    def getDdcRateList(cls,index=None):
        rateDict = cls.getDdcRateSet(index)
        rateKeys = list(rateDict.keys())
        rateKeys.sort()
        return [rateDict[k] for k in rateKeys]

    # EXTENSION
    ##
    # Gets the set of available DDC rates.
    #
    # \param index If not None, get the set for the DDC with the given index.
    # \return A set of DDC rates.  This is a dictionary where the keys are
    #    rate indices and the values are the corresponding DDC rates.
    @classmethod
    def getDdcRateSet(cls, index=None):
        return cls.rateSet

    # EXTENSION
    ##
    # Gets the set of available DDC data formats.
    #
    # \return A dictionary where the keys are rate indices and the values are
    #    the corresponding DDC data formats ("iq" or "real").
    @classmethod
    def getDdcDataFormat(cls,):
        return dict( (i,cls.dataFormat.get(i,DDC_DATA_FORMAT.IQ)) for i in list(cls.getDdcRateSet().keys()) )

    # EXTENSION
    ##
    # Gets the list of available DDC bandwidths.
    #
    # \param index If not None, get the list for the DDC with the given index.
    # \return A list of DDC bandwidths.
    @classmethod
    def getDdcBwList(cls,index=None):
        bwDict = cls.getDdcBwSet(index)
        rateKeys = list(bwDict.keys())
        rateKeys.sort()
        return [bwDict[k] for k in rateKeys]

    # EXTENSION
    ##
    # Gets the set of available DDC bandwidths.
    #
    # \param index If not None, get the set for the DDC with the given index.
    # \return A set of DDC rates.  This is a dictionary where the keys are
    #    rate indices and the values are the corresponding DDC bandwidths.
    @classmethod
    def getDdcBwSet(cls,index=None):
        return cls.bwSet

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.frqCmd is not None:
            cmd = self.frqCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                freq = rspInfo.get(configKeys.DDC_FREQUENCY_OFFSET, None)
                self.configuration[configKeys.DDC_FREQUENCY_OFFSET] = None if freq is None else \
                                                                      freq * self.frqUnits
        if self.cfgCmd is not None:
            cmd = self.cfgCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            #self.logIfVerbose("rspInfo =", rspInfo)
            if rspInfo is not None:
                for key in [configKeys.DDC_RATE_INDEX,
                            configKeys.DDC_UDP_DESTINATION,
                            configKeys.ENABLE,
                            configKeys.DDC_VITA_ENABLE,
                            configKeys.DDC_STREAM_ID]:
                    self.configuration[key] = rspInfo.get(key, None)
        if self.dportCmd is not None:
            cmd = self.dportCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            #self.logIfVerbose("rspInfo =", rspInfo)
            if rspInfo is not None:
                for key in [configKeys.DDC_DATA_PORT,
                            ]:
                    self.configuration[key] = rspInfo.get(key, None)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if self.dportCmd is not None and \
           configKeys.DDC_DATA_PORT in confDict:
            cDict = {}
            self._dictUpdate(cDict, confDict, self.configuration,
                             [configKeys.DDC_DATA_PORT])
            cDict.update({ "parent": self,
                            configKeys.INDEX: self.index,
                             "verbose": self.verbose,
                             "logFile": self.logFile })
            cmd = self.dportCmd(**cDict)
            ret &= cmd.send( self.callback, )
            ret &= cmd.success
            self._addLastCommandErrorInfo(cmd)
            if ret:
                self.configuration[configKeys.DDC_DATA_PORT] = getattr(cmd, configKeys.DDC_DATA_PORT)
            pass
        if configKeys.DDC_FREQUENCY_OFFSET in confDict:
            if self.frqCmd is not None:
                freqIn = float(confDict.get(configKeys.DDC_FREQUENCY_OFFSET, 0))
                freqAdj = adjustFrequency(freqIn, self.frqRange,
                                          self.frqRes, self.frqUnits)
                cmd = self.frqCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.DDC_FREQUENCY_OFFSET: freqAdj,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.DDC_FREQUENCY_OFFSET] = freqAdj * self.frqUnits
                pass
        keys = [configKeys.ENABLE,
                configKeys.DDC_RATE_INDEX,
                configKeys.DDC_UDP_DESTINATION,
                configKeys.DDC_VITA_ENABLE,
                configKeys.DDC_STREAM_ID]
        if any([q in confDict for q in keys]):
            if self.cfgCmd is not None:
                cDict = {}
                self._dictUpdate(cDict, confDict, self.configuration, keys)
                cDict.update({ "parent": self,
                                configKeys.INDEX: self.index,
                                 "verbose": self.verbose,
                                 "logFile": self.logFile })
                cmd = self.cfgCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    for key in keys:
                        self.configuration[key] = getattr(cmd, key)
                pass
        return ret

#--  WBDDC Objects  ---------------------------------------------------------#

##
# Base WBDDC component class.
#
# A radio handler object maintains one WBDDC component object per WBDDC
# on the radio.
#
class _wbddc(_ddc):
    _name = "WBDDC"
    wideband = True
    cfgCmd = command.wbddc
    frqCmd = command.wbfrq

#--  NBDDC Objects  ---------------------------------------------------------#

##
# Base NBDDC component class.
#
# A radio handler object maintains one NBDDC component object per NBDDC
# on the radio.
#
class _nbddc(_ddc):
    _name = "NBDDC"
    wideband = False
    tunable = True
    cfgCmd = command.nbddc
    frqCmd = command.nbfrq
    # OVERRIDE
    validConfigurationKeywords = [
                                  configKeys.ENABLE,
                                  configKeys.DDC_FREQUENCY_OFFSET,
                                  configKeys.DDC_RATE_INDEX,
                                  configKeys.DDC_UDP_DESTINATION,
                                  configKeys.DDC_VITA_ENABLE,
                                  configKeys.DDC_STREAM_ID,
                                  configKeys.NBDDC_RF_INDEX,
                                  ]

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.frqCmd is not None and self.tunable:
            cmd = self.frqCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                freq = rspInfo.get(configKeys.DDC_FREQUENCY_OFFSET, None)
                self.configuration[configKeys.DDC_FREQUENCY_OFFSET] = None if freq is None else \
                                                                      freq * self.frqUnits
        if self.cfgCmd is not None:
            cmd = self.cfgCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            #self.logIfVerbose("rspInfo =", rspInfo)
            if rspInfo is not None:
                for key in [configKeys.ENABLE,
                            configKeys.DDC_RATE_INDEX,
                            configKeys.DDC_UDP_DESTINATION,
                            configKeys.DDC_VITA_ENABLE,
                            configKeys.DDC_STREAM_ID,
                            configKeys.NBDDC_RF_INDEX]:
                    self.configuration[key] = rspInfo.get(key, None)
        pass

    # OVERRIDE
    ##
    # \protected
    # Sets the component's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if configKeys.DDC_FREQUENCY_OFFSET in confDict:
            if self.frqCmd is not None:
                freqIn = float(confDict.get(configKeys.DDC_FREQUENCY_OFFSET, 0))
                freqAdj = adjustFrequency(freqIn, self.frqRange,
                                          self.frqRes, self.frqUnits)
                cmd = self.frqCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.DDC_FREQUENCY_OFFSET: freqAdj,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.DDC_FREQUENCY_OFFSET] = freqAdj * self.frqUnits
                pass
        keys = [configKeys.ENABLE,
                configKeys.DDC_RATE_INDEX,
                configKeys.DDC_UDP_DESTINATION,
                configKeys.DDC_VITA_ENABLE,
                configKeys.DDC_STREAM_ID,
                configKeys.NBDDC_RF_INDEX]
        if any([q in confDict for q in keys]):
            if self.cfgCmd is not None:
                cDict = {}
                self._dictUpdate(cDict, confDict, self.configuration, keys)
                cDict.update({ "parent": self,
                                configKeys.INDEX: self.index,
                                 "verbose": self.verbose,
                                 "logFile": self.logFile })
                cmd = self.cfgCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    for key in keys:
                        self.configuration[key] = getattr(cmd, key)
                pass
        return ret

#----------------------------------------------------------------------------#
#--  Tone Generator Objects  ------------------------------------------------#

##
# Base continuous-wave (CW) tone generator component class.
#
# Depending on the radio, the tone generator component can be maintained
# at the radio level or at the transmitter level (as a sub-component).
#
class _cwToneGen(_base):
    _name = "CWToneGen"
    ## Tone frequency range.  This is a 2-tuple: (minimum, maximum).
    frqRange = (-512e5,512e5)
    ## Tone frequency resolution.
    frqRes = 1
    ## Tone amplitude range.  This is a 2-tuple: (minimum, maximum).
    ampRange = (0, 65535)
    ## Tone amplitude resolution.
    ampRes = 1
    ## Tone phase range.  This is a 2-tuple: (minimum, maximum).
    phaseRange = (-180, 180)
    ## Tone phase resolution.
    phaseRes = 1
    ## Tone sweep start frequency range.  This is a 2-tuple: (minimum, maximum).
    startRange = (-512e5,512e5)
    ## Tone sweep start frequency resolution.
    startRes = 1
    ## Tone sweep stop frequency range.  This is a 2-tuple: (minimum, maximum).
    stopRange = (-512e5,512e5)
    ## Tone sweep stop frequency resolution.
    stopRes = 1
    ## Tone sweep step frequency range.  This is a 2-tuple: (minimum, maximum).
    stepRange = (-512e5,512e5)
    ## Tone sweep step frequency resolution.
    stepRes = 1
    ## Tone sweep dwell time range.  This is a 2-tuple: (minimum, maximum).
    dwellRange = (0, 0xFFFFFFFF)
    ## Tone sweep dwell time resolution.
    dwellRes = 1
    ## Command: CW tone characteristics set/query
    toneCmd = command.cwt
    ## Command: CW tone sweep set/query
    sweepCmd = command.cws

    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                configKeys.CW_FREQUENCY,
                                configKeys.CW_AMPLITUDE,
                                configKeys.CW_PHASE,
                                configKeys.CW_SWEEP_START,
                                configKeys.CW_SWEEP_STOP,
                                configKeys.CW_SWEEP_STEP,
                                configKeys.CW_SWEEP_DWELL,
                                  ]

    ##
    # Constructs a tone generator component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self,*args,**kwargs)

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.toneCmd is not None:
            cmd = self.toneCmd(**{ "parent": self,
                                   configKeys.CW_INDEX: self.index, \
                                   configKeys.TX_INDEX: self.parent.index, \
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.CW_FREQUENCY] = rspInfo.get(configKeys.CW_FREQUENCY, None)
                self.configuration[configKeys.CW_AMPLITUDE] = rspInfo.get(configKeys.CW_AMPLITUDE, None)
                self.configuration[configKeys.CW_PHASE] = rspInfo.get(configKeys.CW_PHASE, None)
        if self.sweepCmd is not None:
            cmd = self.sweepCmd(**{ "parent": self,
                                   configKeys.CW_INDEX: self.index, \
                                   configKeys.TX_INDEX: self.parent.index, \
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.CW_SWEEP_START] = rspInfo.get(configKeys.CW_SWEEP_START, None)
                self.configuration[configKeys.CW_SWEEP_STOP] = rspInfo.get(configKeys.CW_SWEEP_STOP, None)
                self.configuration[configKeys.CW_SWEEP_STEP] = rspInfo.get(configKeys.CW_SWEEP_STEP, None)
                self.configuration[configKeys.CW_SWEEP_DWELL] = rspInfo.get(configKeys.CW_SWEEP_DWELL, None)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        keys = [configKeys.CW_FREQUENCY,
                configKeys.CW_AMPLITUDE,
                configKeys.CW_PHASE,
                ]
        if any([q in confDict for q in keys]):
            if self.toneCmd is not None:
                cDict = {}
                self._dictUpdate(cDict, confDict, self.configuration, keys)
                cDict.update({ "parent": self,
                                configKeys.CW_INDEX: self.index,
                                configKeys.TX_INDEX: self.parent.index,
                                 "verbose": self.verbose,
                                 "logFile": self.logFile })
                cmd = self.toneCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    for key in keys:
                        self.configuration[key] = getattr(cmd, key)
                pass
        keys = [configKeys.CW_SWEEP_START,
                configKeys.CW_SWEEP_STOP,
                configKeys.CW_SWEEP_STEP,
                configKeys.CW_SWEEP_DWELL,
                ]
        if any([q in confDict for q in keys]):
            if self.sweepCmd is not None:
                cDict = {}
                self._dictUpdate(cDict, confDict, self.configuration, keys)
                cDict.update({ "parent": self,
                                configKeys.CW_INDEX: self.index,
                                configKeys.TX_INDEX: self.parent.index,
                                 "verbose": self.verbose,
                                 "logFile": self.logFile })
                cmd = self.sweepCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    for key in keys:
                        self.configuration[key] = getattr(cmd, key)
                pass
        return ret

#----------------------------------------------------------------------------#
#--  Transmitter Objects  ---------------------------------------------------#

##
# Base transmitter component class.
#
# A radio handler object maintains one transmitter component object per
# transmitter on the radio.
#
class _tx(_base):
    _name = "TX"
    ## Tunable frequency range.  This is a 2-tuple: (minimum, maximum).
    frqRange = (20e6,6e9)
    ## Frequency resolution.
    frqRes = 1e6
    ## Frequency units.
    frqUnits = 1e6
    ## Tunable attenuation range.  This is a 2-tuple: (minimum, maximum).
    attRange = (0.0,10.0)
    ## Attenuation resolution.
    attRes = 1.0
    ## Number of tone generators.
    numToneGen = 0
    ## Tone generator component type.
    toneGenType = None
    ## Tone generator index base (where index numbers start from).
    toneGenIndexBase = 1
    # Supported commands
    ## Frequency set/query command.
    frqCmd = command.txf
    ## Attenuation set/query command.
    attCmd = command.txa
    ## TX power set/query command.
    tpwrCmd = command.txp
    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                   configKeys.TX_FREQUENCY,
                                   configKeys.TX_ATTENUATION,
                                   configKeys.ENABLE,
                                   configKeys.CONFIG_CW,
                                  ]

    ##
    # Constructs a transmitter component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self,*args,**kwargs)
        self.toneGenDict = {}
        for i in range(self.toneGenIndexBase,
                        self.toneGenIndexBase + self.numToneGen, 1):
            self.toneGenDict[i] = self.toneGenType(parent=self,
                                                   callback=self.callback,
                                                   index=i,
                                                   verbose=self.verbose,
                                                   logFile=self.logFile)

    # OVERRIDE
    ##
    # Adds a communication transport to this component.
    #
    # The component will query the hardware for its configuration after the
    # transport is added.
    #
    # \param transport The communication transport (an object of type
    #    CyberRadioDriver.transport.radio_transport).
    # \param callback A method that the component uses to send data over the
    #   connected transport.
    # \param queryConfig Whether or not to query the configuration
    #   through hardware commands
    def addTransport(self,transport,callback,queryConfig=True):
        for i in self.toneGenDict:
            self.toneGenDict[i].addTransport(transport, callback, queryConfig)
        _base.addTransport(self, transport, callback, queryConfig)

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.tpwrCmd is not None:
            cmd = self.tpwrCmd(**{ "parent": self,
                                   configKeys.TX_INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.ENABLE] = rspInfo.get(configKeys.ENABLE, 0)
        if self.frqCmd is not None:
            cmd = self.frqCmd(**{ "parent": self,
                                   configKeys.TX_INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                freq = rspInfo.get(configKeys.TX_FREQUENCY, None)
                self.configuration[configKeys.TX_FREQUENCY] = None if freq is None else \
                                                  freq * self.frqUnits
        if self.attCmd is not None:
            cmd = self.attCmd(**{ "parent": self,
                                   configKeys.TX_INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            self._addLastCommandErrorInfo(cmd)
            cmd.send( self.callback, )
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.TX_ATTENUATION] = rspInfo.get(configKeys.TX_ATTENUATION, None)
        if self.toneGenType is not None:
            self.configuration[configKeys.CONFIG_CW] = {}
            for i in self.toneGenDict:
                self.configuration[configKeys.CONFIG_CW][i] = self.toneGenDict[i].queryConfiguration()
                self.cmdErrorInfo.extend(self.toneGenDict[i].getLastCommandErrorInfo())
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if configKeys.ENABLE in confDict:
            if self.tpwrCmd is not None:
                cmd = self.tpwrCmd(**{ "parent": self,
                                       configKeys.TX_INDEX: self.index,
                                       configKeys.ENABLE: confDict.get(configKeys.ENABLE, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.ENABLE] = getattr(cmd, configKeys.ENABLE)
                pass
        if configKeys.TX_FREQUENCY in confDict:
            if self.frqCmd is not None:
                freqIn = float(confDict.get(configKeys.TX_FREQUENCY, 0))
                freqAdj = adjustFrequency(freqIn, self.frqRange,
                                          self.frqRes, self.frqUnits)
                cmd = self.frqCmd(**{ "parent": self,
                                       configKeys.TX_INDEX: self.index,
                                       configKeys.TX_FREQUENCY: freqAdj,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TX_FREQUENCY] = freqAdj * self.frqUnits
                pass
        if configKeys.TX_ATTENUATION in confDict:
            if self.attCmd is not None:
                rfAttIn = float(confDict.get(configKeys.TX_ATTENUATION, 0))
                rfAttAdj = adjustAttenuation(rfAttIn, self.attRange,
                                            self.attRes, 1)
                cmd = self.attCmd(**{ "parent": self,
                                       configKeys.TX_INDEX: self.index,
                                       configKeys.TX_ATTENUATION: rfAttAdj,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.TX_ATTENUATION] = rfAttAdj
                pass
        if self.numToneGen > 0 and configKeys.CONFIG_CW in confDict:
            for tgNum in range(self.toneGenIndexBase, self.toneGenIndexBase + self.numToneGen):
                if tgNum in confDict[configKeys.CONFIG_CW]:
                    self.toneGenDict[tgNum].setConfiguration(**confDict[configKeys.CONFIG_CW][tgNum])
                    self.cmdErrorInfo.extend(self.toneGenDict[tgNum].getLastCommandErrorInfo())
                pass
        return ret

#----------------------------------------------------------------------------#
#--  DUC Objects  ---------------------------------------------------#

class _duc(_base):
    _name = "_baseDuc"
    ## Whether this is a wideband DUC.
    wideband = True
    ## Tunable frequency offset range.  This is a 2-tuple: (minimum, maximum).
    frqRange = (-51.2e6, 51.2e6)
    ## Frequency offset resolution.
    frqRes = 1
    ## Frequency offset units.
    frqUnits = 1
    ## Tunable attenuation range.  This is a 2-tuple: (minimum, maximum).
    attRange = (-60.0,60.0)
    ## Attenuation resolution.
    attRes = 0.1
    ## DUC rate set.  This is a dictionary whose keys are rate index numbers and
    # whose values are DUC rates.
    rateSet = { \
                0: 102.4e6,
                1:  51.2e6,
                2:  25.6e6,
                3:  12.8e6,
                4:   6.4e6,
                6:   1.6e6,
                7: 800.0e3,
                8: 400.0e3,
                9: 200.0e3,
               10: 100.0e3,
               11:  50.0e3,
               12:  25.0e3,
               13:  12.5e3,
                16: 13e6/48,
               20:   5.6e6,
                 }
    ## DUC configuration query/set command.
    cfgCmd = command.duc
    ## DUC load snapshot command [set only].
    snapshotLoadCmd = None
    ## DUC transmit snapshot query/set command.
    snapshotTxCmd = None
    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                  configKeys.DUC_DATA_PORT,
                                  configKeys.DUC_FREQUENCY,
                                  configKeys.DUC_ATTENUATION,
                                  configKeys.DUC_RATE_INDEX,
                                  configKeys.DUC_TX_CHANNELS,
                                  configKeys.DUC_MODE,
                                  configKeys.DUC_STREAM_ID,
                                  #configKeys.DUC_START_BLOCK,
                                  #configKeys.DUC_END_BLOCK,
                                  configKeys.DUC_FILENAME,
                                  configKeys.DUC_START_SAMPLE,
                                  configKeys.DUC_SAMPLES,
                                  configKeys.DUC_SINGLE_PLAYBACK,
                                  ]

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.cfgCmd is not None:
            cmd = self.cfgCmd(**{ "parent": self,
                                  configKeys.DUC_INDEX: self.index,
                                  "query": True,
                                   "verbose": self.verbose,
                                   "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.DUC_DATA_PORT] = rspInfo.get(configKeys.DUC_DATA_PORT, 0)
                self.configuration[configKeys.DUC_FREQUENCY] = rspInfo.get(configKeys.DUC_FREQUENCY, 0)
                self.configuration[configKeys.DUC_ATTENUATION] = rspInfo.get(configKeys.DUC_ATTENUATION, 0)
                self.configuration[configKeys.DUC_RATE_INDEX] = rspInfo.get(configKeys.DUC_RATE_INDEX, 0)
                self.configuration[configKeys.DUC_TX_CHANNELS] = rspInfo.get(configKeys.DUC_TX_CHANNELS, 0)
                self.configuration[configKeys.DUC_MODE] = rspInfo.get(configKeys.DUC_MODE, 0)
                self.configuration[configKeys.DUC_STREAM_ID] = rspInfo.get(configKeys.DUC_STREAM_ID, 0)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        keys = [
            configKeys.DUC_DATA_PORT,
            configKeys.DUC_FREQUENCY,
            configKeys.DUC_ATTENUATION,
            configKeys.DUC_RATE_INDEX,
            configKeys.DUC_TX_CHANNELS,
            configKeys.DUC_MODE,
            configKeys.DUC_STREAM_ID,
            ]
        if any([q in confDict for q in keys]):
            if self.cfgCmd is not None:
                cDict = {}
                self._dictUpdate(cDict, confDict, self.configuration, keys)
                cDict.update({ "parent": self,
                                configKeys.DUC_INDEX: self.index,
                                 "verbose": self.verbose,
                                 "logFile": self.logFile })
                cmd = self.cfgCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.DUC_DATA_PORT] = getattr(cmd, configKeys.DUC_DATA_PORT)
                    self.configuration[configKeys.DUC_FREQUENCY] = getattr(cmd, configKeys.DUC_FREQUENCY)
                    self.configuration[configKeys.DUC_ATTENUATION] = getattr(cmd, configKeys.DUC_ATTENUATION)
                    self.configuration[configKeys.DUC_RATE_INDEX] = getattr(cmd, configKeys.DUC_RATE_INDEX)
                    self.configuration[configKeys.DUC_TX_CHANNELS] = getattr(cmd, configKeys.DUC_TX_CHANNELS)
                    self.configuration[configKeys.DUC_MODE] = getattr(cmd, configKeys.DUC_MODE)
                    self.configuration[configKeys.DUC_STREAM_ID] = getattr(cmd, configKeys.DUC_STREAM_ID)
                pass
        # DUC snapshot load command comes first, so that the user can provide snapshot
        # load information and snapshot transmission information in one setConfiguration()
        # command.
        keys = [
            configKeys.DUC_FILENAME,
            configKeys.DUC_START_SAMPLE,
            configKeys.DUC_SAMPLES,
            ]
        if all([q in confDict for q in keys]):
            if self.snapshotLoadCmd is not None:
                cmd = self.snapshotLoadCmd(**{ "parent": self,
                                      configKeys.DUC_FILENAME: confDict[configKeys.DUC_FILENAME],
                                      configKeys.DUC_INDEX: self.index,
                                      configKeys.DUC_START_SAMPLE: confDict[configKeys.DUC_START_SAMPLE],
                                      configKeys.DUC_SAMPLES: confDict[configKeys.DUC_SAMPLES],
                                       "verbose": self.verbose,
                                       "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                pass
        keys = [
            configKeys.DUC_START_SAMPLE,
            configKeys.DUC_SAMPLES,
            configKeys.DUC_SINGLE_PLAYBACK,
            ]
        if any([q in confDict for q in keys]):
            if self.snapshotTxCmd is not None:
                cDict = {}
                self._dictUpdate(cDict, confDict, self.configuration, keys)
                cDict.update({ "parent": self,
                                configKeys.DUC_INDEX: self.index,
                                configKeys.DUC_START_SAMPLE: confDict[configKeys.DUC_START_SAMPLE],
                                configKeys.DUC_SAMPLES: confDict[configKeys.DUC_SAMPLES],
                                configKeys.DUC_SINGLE_PLAYBACK: confDict[configKeys.DUC_SINGLE_PLAYBACK],
                                 "verbose": self.verbose,
                                 "logFile": self.logFile })
                cmd = self.snapshotTxCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                pass
        return ret

class _wbduc(_duc):
    _name = "WBDUC"
    wideband = True

#----------------------------------------------------------------------------#
#--  DDC Group Objects  ---------------------------------------------------#

##
# Base DDC group component class.
#
# A DDC group component object maintains one DDC group on the radio.
#
class ddc_group(_base):
    _name = "DDCGroup"
    ## Whether this is a wideband DDC group.
    wideband = True
    ## \brief Group member index base (what number indices start at)
    groupMemberIndexBase = 1
    ## \brief Number of potential group members
    numGroupMembers = 0
    ## DDC group member assignment command
    groupMemberCmd = None
    ## DDC group enable command
    groupEnableCmd = None

    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                  configKeys.ENABLE,
                                  configKeys.DDC_GROUP_MEMBERS,
                                  ]

    ##
    # Constructs a DDC group component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self, *args, **kwargs)

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.groupMemberCmd is not None:
            members = []
            for memberIndex in range(self.groupMemberIndexBase,
                                      self.groupMemberIndexBase + self.numGroupMembers, 1):
                cmd = self.groupMemberCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.DDC_GROUP_MEMBER: memberIndex,
                                       "query": True,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                cmd.send( self.callback, )
                self._addLastCommandErrorInfo(cmd)
                rspInfo = cmd.getResponseInfo()
                if rspInfo is not None:
                    enabled = rspInfo.get(configKeys.ENABLE, False)
                    if enabled:
                        members.append(memberIndex)
            self.configuration[configKeys.DDC_GROUP_MEMBERS] = members
        if self.groupEnableCmd is not None:
            cmd = self.groupEnableCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.ENABLE] = rspInfo.get(configKeys.ENABLE, 0)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if self.groupMemberCmd is not None and \
           configKeys.DDC_GROUP_MEMBERS in confDict:
            if confDict[configKeys.DDC_GROUP_MEMBERS] is None:
                members = []
            elif isinstance(confDict[configKeys.DDC_GROUP_MEMBERS], int):
                members = [confDict[configKeys.DDC_GROUP_MEMBERS]]
            else:
                members = confDict[configKeys.DDC_GROUP_MEMBERS]
            for member in range(self.groupMemberIndexBase,
                                 self.groupMemberIndexBase + self.numGroupMembers):
                enabled = 1 if member in members else 0
                cDict = { "parent": self,
                          configKeys.INDEX: self.index,
                          configKeys.DDC_GROUP_MEMBER: member,
                          configKeys.ENABLE: enabled,
                           "verbose": self.verbose,
                           "logFile": self.logFile }
                cmd = self.groupMemberCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
            if ret:
                self.configuration[configKeys.DDC_GROUP_MEMBERS] = members
            pass
        if configKeys.ENABLE in confDict:
            if self.groupEnableCmd is not None:
                cmd = self.groupEnableCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.ENABLE: confDict.get(configKeys.ENABLE, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.ENABLE] = getattr(cmd, configKeys.ENABLE)
                pass
        return ret

##
# WBDDC group component class.
#
# A WBDDC group component object maintains one WBDDC group on the radio.
#
class wbddc_group(ddc_group):
    _name = "WBDDCGroup"
    ## Whether this is a wideband DDC group.
    wideband = True
    ## DDC group member assignment command
    groupMemberCmd = command.wbg
    ## DDC group enable command
    groupEnableCmd = command.wbge

##
# NBDDC group component class.
#
# A NBDDC group component object maintains one NBDDC group on the radio.
#
class nbddc_group(ddc_group):
    _name = "NBDDCGroup"
    ## Whether this is a wideband DDC group.
    wideband = False
    ## DDC group member assignment command
    groupMemberCmd = command.nbg
    ## DDC group enable command
    groupEnableCmd = command.nbge

##
# Base DUC group component class.
#
# A DUC group component object maintains one DUC group on the radio.
#
class duc_group(_base):
    _name = "DUCGroup"
    ## Whether this is a wideband DUC group.
    wideband = True
    ## \brief Group member index base (what number indices start at)
    groupMemberIndexBase = 1
    ## \brief Number of potential group members
    numGroupMembers = 0
    ## DUC group member assignment command
    groupMemberCmd = None
    ## DUC group enable command
    groupEnableCmd = None

    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                  configKeys.ENABLE,
                                  configKeys.DUC_GROUP_MEMBERS,
                                  ]

    ##
    # Constructs a DUC group component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self, *args, **kwargs)

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.groupMemberCmd is not None:
            members = []
            for memberIndex in range(self.groupMemberIndexBase,
                                      self.groupMemberIndexBase + self.numGroupMembers, 1):
                cmd = self.groupMemberCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.DUC_GROUP_MEMBER: memberIndex,
                                       "query": True,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                cmd.send( self.callback, )
                self._addLastCommandErrorInfo(cmd)
                rspInfo = cmd.getResponseInfo()
                if rspInfo is not None:
                    enabled = rspInfo.get(configKeys.ENABLE, False)
                    if enabled:
                        members.append(memberIndex)
            self.configuration[configKeys.DUC_GROUP_MEMBERS] = members
        if self.groupEnableCmd is not None:
            cmd = self.groupEnableCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.ENABLE] = rspInfo.get(configKeys.ENABLE, 0)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if self.groupMemberCmd is not None and \
           configKeys.DUC_GROUP_MEMBERS in confDict:
            if confDict[configKeys.DUC_GROUP_MEMBERS] is None:
                members = []
            elif isinstance(confDict[configKeys.DUC_GROUP_MEMBERS], int):
                members = [confDict[configKeys.DUC_GROUP_MEMBERS]]
            else:
                members = confDict[configKeys.DUC_GROUP_MEMBERS]
            for member in range(self.groupMemberIndexBase,
                                 self.groupMemberIndexBase + self.numGroupMembers):
                enabled = 1 if member in members else 0
                cDict = { "parent": self,
                          configKeys.INDEX: self.index,
                          configKeys.DUC_GROUP_MEMBERS: member,
                          configKeys.ENABLE: enabled,
                           "verbose": self.verbose,
                           "logFile": self.logFile }
                cmd = self.groupMemberCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
            if ret:
                self.configuration[configKeys.DUC_GROUP_MEMBERS] = members
            pass
        if configKeys.ENABLE in confDict:
            if self.groupEnableCmd is not None:
                cmd = self.groupEnableCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.ENABLE: confDict.get(configKeys.ENABLE, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.ENABLE] = getattr(cmd, configKeys.ENABLE)
                pass
        return ret

##
# WBDUC group component class.
#
# A WBDUC group component object maintains one WBDUC group on the radio.
#
class wbduc_group(ddc_group):
    _name = "WBDUCGroup"
    ## Whether this is a wideband DUC group.
    wideband = True
    ## DUC group member assignment command
    groupMemberCmd = command.ducg
    ## DUC group enable command
    groupEnableCmd = command.ducge

##
# Base tuner group component class.
#
# A tuner group component object maintains one tuner group on the radio.
#
# \note Tuner groups are normally used for coherent tuning support.
#
class tuner_group(_base):
    _name = "TunerGroup"
    ## \brief Group member index base (what number indices start at)
    groupMemberIndexBase = 1
    ## \brief Number of potential group members
    numGroupMembers = 0
    ## Group member assignment command
    groupMemberCmd = None
    ## Group enable command
    groupEnableCmd = None

    # OVERRIDE
    ##
    # The list of valid configuration keywords supported by this
    # object.  Override in derived classes as needed.
    validConfigurationKeywords = [
                                  configKeys.ENABLE,
                                  configKeys.TUNER_GROUP_MEMBERS,
                                  ]

    ##
    # Constructs a tuner group component object.
    #
    # The constructor uses keyword arguments to configure the class.  It
    # consumes the following keyword arguments:
    #

    #
 "verbose": Verbose mode (Boolean)
    #
 "logFile": An open file or file-like object to be used for log output.
    #    If not provided, this defaults to standard output.
    #
 "parent": The radio handler object that manages this component object.
    #
 "callback": A method that the component uses to send data over a
    #   connected transport.
    #
 "index": The index number for this component.
    #

    #
    # \param args Variable-length list of positional arguments.  Positional
    #     arguments are ignored.
    # \param kwargs Dictionary of keyword arguments for the component
    #     object.  Which keyword arguments are valid depends on the
    #     specific component.  Unsupported keyword arguments will be ignored.
    def __init__(self,*args,**kwargs):
        _base.__init__(self, *args, **kwargs)

    # OVERRIDE
    ##
    # \protected
    # Queries hardware to determine the object's current configuration.
    def _queryConfiguration(self):
        # Call the base-class implementation
        configKeys.Configurable._queryConfiguration(self)
        # Override
        if self.groupMemberCmd is not None:
            members = []
            for memberIndex in range(self.groupMemberIndexBase,
                                      self.groupMemberIndexBase + self.numGroupMembers, 1):
                cmd = self.groupMemberCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.TUNER_GROUP_MEMBER: memberIndex,
                                       "query": True,
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                cmd.send( self.callback, )
                self._addLastCommandErrorInfo(cmd)
                rspInfo = cmd.getResponseInfo()
                if rspInfo is not None:
                    enabled = rspInfo.get(configKeys.ENABLE, False)
                    if enabled:
                        members.append(memberIndex)
            self.configuration[configKeys.TUNER_GROUP_MEMBERS] = members
        if self.groupEnableCmd is not None:
            cmd = self.groupEnableCmd(**{ "parent": self,
                                   configKeys.INDEX: self.index,
                                   "query": True,
                                    "verbose": self.verbose,
                                    "logFile": self.logFile })
            cmd.send( self.callback, )
            self._addLastCommandErrorInfo(cmd)
            rspInfo = cmd.getResponseInfo()
            if rspInfo is not None:
                self.configuration[configKeys.ENABLE] = rspInfo.get(configKeys.ENABLE, 0)
        pass

    # OVERRIDE
    ##
    # \protected
    # Issues hardware commands to set the object's current configuration.
    def _setConfiguration(self, confDict):
        ret = True
        if self.groupMemberCmd is not None and \
           configKeys.TUNER_GROUP_MEMBERS in confDict:
            if confDict[configKeys.TUNER_GROUP_MEMBERS] is None:
                members = []
            elif isinstance(confDict[configKeys.TUNER_GROUP_MEMBERS], int):
                members = [confDict[configKeys.TUNER_GROUP_MEMBERS]]
            else:
                members = confDict[configKeys.TUNER_GROUP_MEMBERS]
            for member in range(self.groupMemberIndexBase,
                                 self.groupMemberIndexBase + self.numGroupMembers):
                enabled = 1 if member in members else 0
                cDict = { "parent": self,
                          configKeys.INDEX: self.index,
                          configKeys.TUNER_GROUP_MEMBERS: member,
                          configKeys.ENABLE: enabled,
                           "verbose": self.verbose,
                           "logFile": self.logFile }
                cmd = self.groupMemberCmd(**cDict)
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
            if ret:
                self.configuration[configKeys.TUNER_GROUP_MEMBERS] = members
            pass
        if configKeys.ENABLE in confDict:
            if self.groupEnableCmd is not None:
                cmd = self.groupEnableCmd(**{ "parent": self,
                                       configKeys.INDEX: self.index,
                                       configKeys.ENABLE: confDict.get(configKeys.ENABLE, 0),
                                        "verbose": self.verbose,
                                        "logFile": self.logFile })
                ret &= cmd.send( self.callback, )
                ret &= cmd.success
                self._addLastCommandErrorInfo(cmd)
                if ret:
                    self.configuration[configKeys.ENABLE] = getattr(cmd, configKeys.ENABLE)
                pass
        return ret


import os
from json import dump, load
from collections import OrderedDict, defaultdict
import numpy as np
from cycgkit.cgtypes import mat4, vec3
from freetype import Face

from ..Base3DObjectClass import DefaultObjectParameters
from .LayerClass import Layer
from .FontRendering import CharRangesEnum
from .FontRendering.MSDFAtlasRenderer import NAMEFORMATSTRING, render, AtlasInfo, CharData
from ..backends.base_backend import DrawingData, InstanceData
from ..model_management.MeshClass import Mesh, NormalsCalculationTypeEnum, UVCalculationTypeEnum
from ..scene_management.SceneClass import DefaultSceneParameters
from ..events_processing.EventsListenerClass import EventsListener

SAVEFORMAT = 'png'
DEFAULT2DSHADERID = 'default2D'
DEFAULT2DTEXTSHADERID = 'default2Dtext'
INFOFORMATSTRING = '{fontPath}.info'
FONTTEXTUREIDSTRING = '{}_fonttex'

class GuiManager:
    def __init__(self):
        self.fontSizes = defaultdict(dict)
        self.fontFilePaths = {}
        self.fontTextureNames = {}
        self.engine = None
        self.backend = None
        self._currentMat = None
        self._layers = {}
        self._layersOrder = []
        self._objectIDs = []
        self.view = mat4.identity()
        self.defaultTexture = None
        self.fontInfos = {}
        self._window = None
        self.eventsListener = EventsListener()
        eventsListener = self.eventsListener
        eventsListener.onWindowEvent = self._onWindowEventCallback
        eventsListener.onMouseEvent = self._onMouseEventCallback

        from .._engine import PathPiece
        self.fontsCache = PathPiece((os.path.dirname(__file__), 'cache'))
        if not os.path.exists(self.fontsCache):
            os.mkdir(self.fontsCache)

        vertices = np.array([[-.5, .5, 0], [-.5, -.5, 0], [.5, -.5, 0], [.5, .5, 0]], dtype=np.float32)
        faces = np.array([[0, 1, 2], [0, 2, 3]], dtype=np.int32)
        self.quadmesh = Mesh.fromObjectInfo(vertices, faces, ([0, 0, 0], [1, 1, 1]), UVCalculationTypeEnum.planar,
                                            NormalsCalculationTypeEnum.hard, materialIndex=1)

        '''
        orthographic:

        GLdouble  left,
        GLdouble  right,
        GLdouble  bottom,
        GLdouble  top,
        GLdouble  nearVal,
        GLdouble  farVal
        '''
        self.projectionMatrix = mat4.orthographic(0, 1, 0, 1, 0, -1)

    def initialize(self, engine, backend, defaultTexture, window):
        self.engine = engine
        shadersDefaultPath = engine.path.defaults.shaders
        self.backend = backend
        self._window = window
        # load default 2d shader
        p = os.path.join(shadersDefaultPath, 'default_2D_')
        vsp = p + 'VS.glsl'
        fsp = p + 'FS.glsl'
        backend._shaders.loadShader(vsp, fsp, DEFAULT2DSHADERID)

        # store ref to default texture
        self.defaultTexture = defaultTexture

        # load default font
        self.loadFont('default', os.path.join(self.engine.path.defaults.fonts, 'code', 'Code200365k.ttf'))

        self.resizeProjection()
        self._window.events.addListener('_winreslistgui', self.eventsListener)

    def addLayer(self, ID, order=-1, visible=True):
        """

        @rtype : e3d.gui.LayerClass.Layer
        @type order: int
        @type ID: str
        @param visible:
        @type visible:
        """
        if ID in self._layers:
            raise KeyError('ID already exist.')
        else:
            layer = Layer(ID, self, visible)
            if order == -1 or len(self._layers) < order + 1:
                self._layersOrder.append(layer)
            else:
                self._layersOrder.insert(order + 1, layer)

            self._layers[ID] = layer
            return layer

    def moveLayerTo(self, ID, position):
        """
        Move layer 'ID' from its place to 'position'

        @type ID: str
        @type position: : int
        """
        layer = self._layers[ID]
        order = self._layersOrder
        order.remove(layer)
        order.insert(position, layer)

    def deleteLayer(self, ID):
        """

        @type ID: str
        """
        # fixme!
        raise NotImplementedError('Delete layer in GuiManager. Please report it.')

    def hasLayerID(self, ID):
        return ID in self._layers

    def getLayerOrder(self, layerID):
        return self._layersOrder.__getitem__(self._layers[layerID])

    @staticmethod
    def orderInstances(instances):
        orderedPerShader = defaultdict(list)
        for key, appendable in instances.items():
            for i in appendable:
                orderedPerShader[i._stuff[0]._shaderID].append(i)

            finals = []
            for val in orderedPerShader.values():
                finals.extend(val)

            instances.clear()
            instances[key].extend(finals)

    def updateGui(self, windowSize):
        newDrawingData = DrawingData()

        defaultSceneParams = DefaultSceneParameters()
        defaultSceneParams.defaultTexture = self.defaultTexture
        defaultSceneParams.view = self.view
        defaultSceneParams.projection = self.projectionMatrix
        defaultSceneParams.windowSize = vec3(windowSize[0], windowSize[1], 1)
        defaultSceneParams.construct()
        newDrawingData.defaultSceneParams = defaultSceneParams

        newDrawingData.meshes.add(self.quadmesh)

        for layer in reversed(self._layersOrder):
            if layer.visible:
                layer._update()
                for child in reversed(layer._children):
                    if child.visible and not child.isOutBounds():
                        self._buildLayerDrawingData(child, newDrawingData)

        # GuiManager.orderInstances(newDrawingData.instances)  # todo: implement GUI z-ordering to enable this
        return newDrawingData

    def _buildLayerDrawingData(self, currentControl, layerDrawingData):
        defaultObjectParams = DefaultObjectParameters()
        defaultObjectParams.view = self.view
        defaultObjectParams.projection = self.projectionMatrix
        defaultObjectParams.hasBones = False

        defaultObjectParams.model = currentControl.transformation
        defaultObjectParams.construct()

        meshid = self.quadmesh.ID
        meshMat = currentControl._material
        layerDrawingData.instances[meshid].append(InstanceData(meshMat, defaultObjectParams))

        for child in reversed(currentControl._children):
            if child.visible and not child.isOutBounds():
                self._buildLayerDrawingData(child, layerDrawingData)

    @staticmethod
    def convertCharData(data):
        d = OrderedDict()
        for e, v in data.items():
            f = dict()
            u = dir(v)
            for l in u:
                if not l.startswith('_'):
                    val = getattr(v, l)
                    f[l] = val
            d[int(e)] = f
        return d

    @staticmethod
    def convertToJsonable(rangeInfo):
        f = dir(rangeInfo)
        dictio = dict()
        for l in f:
            if not l.startswith('_'):
                if l == 'charDataDict':
                    dictio['finalLocations'] = GuiManager.convertCharData(getattr(rangeInfo, l))
                else:
                    dictio[l] = getattr(rangeInfo, l)

        dictio['size'] = (dictio['width'], dictio['height'])
        dictio['lineMargins'] = (rangeInfo.upperMargin, rangeInfo.lowerMargin)
        return dictio

    @staticmethod
    def convertFromJsoned(rangeInfoDict):
        rangeInfo = AtlasInfo(**rangeInfoDict)
        newCharData = OrderedDict()
        for n, val in rangeInfo.charDataDict.items():
            newCharData[int(n)] = CharData()
            for k in val.keys():
                setattr(newCharData[int(n)], k, val[k])
        rangeInfo.charDataDict = newCharData
        return rangeInfo

    def loadFont(self, ID, fontPath, baseSize=32, maxAtlasWidth=1024, charRange=CharRangesEnum.latin, force=False):
        destinationFolder = self.fontsCache
        fontName = os.path.splitext(os.path.basename(fontPath))[0]
        fullDestPath = os.path.abspath(destinationFolder)
        finalPath = os.path.join(fullDestPath,
                                 NAMEFORMATSTRING.format(fontName=fontName, rangeName=charRange[0], format=SAVEFORMAT))
        infoPath = os.path.splitext(finalPath)[0]
        finalInfoPath = INFOFORMATSTRING.format(fontPath=infoPath)
        if not os.path.exists(finalPath) or force:
            fontRangeInfo = render(fontPath, baseSize, maxAtlasWidth, destinationFolder, SAVEFORMAT, charRange)
            try:
                with open(finalInfoPath, 'w') as dest:
                    dump(fontRangeInfo, dest, default=GuiManager.convertToJsonable)
            except Exception:
                os.remove(finalInfoPath)
                os.remove(finalPath)
                raise
        else:
            with open(finalInfoPath, 'r') as dest:
                fontRangeInfo = GuiManager.convertFromJsoned(load(dest))

        fontTextureName = FONTTEXTUREIDSTRING.format(ID)
        self.engine.textures.loadTexture(finalPath, fontTextureName, mipmapsNumber=0, repeat=False, force=True)
        self.fontInfos[ID] = fontRangeInfo
        self.fontTextureNames[ID] = fontTextureName
        self.fontFilePaths[ID] = fontPath

    def _onWindowEventCallback(self, e):
        if e.eventName == 'resized':
            self.resizeProjection()
            self._resizeLayers()

    def _onMouseEventCallback(self, event):
        activeLayer = None
        for layer in self._layersOrder:
            if layer.visible:
                activeLayer = layer
                break
        if activeLayer is not None:
            activeLayer._handleMouseEvent(event)

    def resizeProjection(self):
        width, height = self._window.size
        self.projectionMatrix = mat4.orthographic(0, width, height, 0, 0, -1)

    def _resizeLayers(self):
        for layer in self._layersOrder:
            if layer.visible:
                layer._resizeCallback()

    def getFontSizeInPixels(self, sizeInPoints, fontID):
        size = self.fontSizes[sizeInPoints].get(fontID)
        if size is None:
            path = self.fontFilePaths[fontID]
            if path is None:
                raise RuntimeError('font info not found. Is the font loaded?')
            face = Face(path)
            hdpi, vdpi = self._window.getCurrentDPIs()
            face.set_char_size(0, sizeInPoints * 64, hdpi, vdpi)
            size = face.size.height / 64
            self.fontSizes[sizeInPoints][fontID] = size
        return size

    def _addControl(self, ID):
        if ID in self._objectIDs:
            raise RuntimeError('the ID \'{}\' is already in use'.format(ID))
        else:
            self._objectIDs.append(ID)


# coding: utf-8
from __future__ import division
import numpy as np
import pdb
import math
from . import data_generators
import copy
class_num =200
part_map_num = {'head':0,'legs':1,'wings':2,'back':3,'belly':4,'breast':5,'tail':6}
part_map_name = {}

net_size=[38,56]
#[head_classifier,legs_classifier,wings_classifier,back_classifier,belly_classifier,breast_classifier,tail_classifier]

#def get_label_from_voc(all_imgs, classes_count, class_mapping,bird_classes_count,bird_class_mapping):

class get_voc_label(object):
    def __init__(self,all_imgs, classes_count, class_mapping,bird_classes_count,bird_class_mapping,trainable=True):
        self.all_imgs = all_imgs
        self.classes_count = classes_count
        self.class_mapping = class_mapping
        self.bird_classes_count = bird_classes_count
        self.bird_class_mapping = bird_class_mapping
        self.max_batch = len(all_imgs)
        self.batch_index = 0
        if trainable:
            self.trainable = 'trainval'
        else:
            self.trainable = 'test'

    def get_next_batch(self):
        img = self.all_imgs[self.batch_index]
        while img['imageset']!= self.trainable:
            self.batch_index+=1
            if self.batch_index>=self.max_batch:
                self.batch_index=0
            img = self.all_imgs[self.batch_index]
        label = self.bird_class_mapping[img['bird_class_name']]
        boxlist =[]
        size_w = img['width']
        size_h = img['height']
        for bbox in img['bboxes']:
            outbox ={}
            outbox['name']=bbox['class']
            cor = np.zeros(4)
            x1 = bbox['x1']
            x2 = bbox['x2']
            y1= bbox['y1']
            y2 = bbox['y2']
            h = y2-y1
            w = x2-x1
            x1/=size_w
            y1/=size_h
            h/=size_h
            w /= size_w
            cor =np.array([x1,y1,w,h])
            outbox['cor'] =cor
            boxlist.append(outbox)
        img_path = img['filepath']
        boxdict, labellist ,labelnpout=self.match(boxlist, label)
        self.batch_index += 1
        if self.batch_index >= self.max_batch:
            self.batch_index = 0
        return img_path,boxdict,labellist,labelnpout

    def match(self,boxlist, label):
        # boxlist的内容是一个dict,name为head,legs等,cor为左上角坐标,宽和长,在0-1之间
        # label的内同是一个数
        labellist = []
        boxdict = {}
        labelnp = np.zeros([1,class_num + 1])
        for i in range(7):
            labellist.append(labelnp)
        labelnpout = np.zeros([7,class_num+1])

        if len(labellist) != 7:
            raise ValueError('SDFA')
        for box in boxlist:
            index = part_map_num[box['name']]
            labellist[index][0][0] = 1
            labellist[index][0][label+1] = 1

            labelnpout[index][0] = 1
            labelnpout[index][label+1] =1
            x = box['cor'][0]
            y = box['cor'][1]
            w = box['cor'][2]
            h = box['cor'][3]
            x *= net_size[1]
            w *= net_size[1]
            y *= net_size[0]
            h *= net_size[0]
            cor_np = np.array([x, y, w, h])
            cor_np = np.expand_dims(cor_np, axis=0)
            cor_np = np.expand_dims(cor_np, axis=0)
            boxdict[box['name']] = cor_np

        npnone = np.zeros([1,1,4])
        # [head_classifier,legs_classifier,wings_classifier,back_classifier,belly_classifier,breast_classifier,tail_classifier]
        cname = ['head','legs','wings','back','belly','breast','tail']
        for onecname in cname:
            if onecname not in boxdict:
                boxdict[onecname] = npnone

        return boxdict, labellist,labelnpout

#boxlist的内容是一个dict,name为head,legs等,cor为左上角坐标,宽和长,在0-1之间
#label的内同是一个数
def match(boxlist,label):
    labellist= []
    boxdict ={}
    labelnp = np.zeros(class_num + 1)
    for i in range(7):
        labellist.append(labelnp)

    if len(labellist)!=7:
        raise ValueError('SDFA')
    for box in boxlist:
        index=part_map_num[box['name']]
        labellist[index][0]=1
        labellist[index][label]=1
        x = box['cor'][0]
        y = box['cor'][1]
        w = box['cor'][2]
        h = box['cor'][3]
        x *= net_size[1]
        w *=net_size[1]
        y *= net_size[0]
        h *=net_size[0]
        cor_np  = np.array([x,y,w,h])
        cor_np =np.expand_dims(cor_np, axis=0)
        cor_np = np.expand_dims(cor_np, axis=0)
        boxdict[box['name']] = cor_np

    return boxdict,labellist


import numpy as np
import torch
import torch.nn as nn

from .anchor_head_template import AnchorHeadTemplate

class AnchorHeadSingle(AnchorHeadTemplate):
    def __init__(self, model_cfg, input_channels, num_class, class_names, grid_size, point_cloud_range,
                 predict_boxes_when_training=True):
        super().__init__(
            model_cfg=model_cfg, num_class=num_class, class_names=class_names, grid_size=grid_size, point_cloud_range=point_cloud_range,
            predict_boxes_when_training=predict_boxes_when_training
        )

        self.num_anchors_per_location = sum(self.num_anchors_per_location)

        self.conv_cls = nn.Conv2d(
            input_channels, self.num_anchors_per_location * self.num_class,
            kernel_size=1
        )
        self.conv_box = nn.Conv2d(
            input_channels, self.num_anchors_per_location * self.box_coder.code_size,
            kernel_size=1
        )

        if self.model_cfg.get('USE_DIRECTION_CLASSIFIER', None) is not None:
            self.conv_dir_cls = nn.Conv2d(
                input_channels,
                self.num_anchors_per_location * self.model_cfg.NUM_DIR_BINS,
                kernel_size=1
            )
        else:
            self.conv_dir_cls = None
        self.init_weights()

    def init_weights(self):
        pi = 0.01
        nn.init.constant_(self.conv_cls.bias, -np.log((1 - pi) / pi))
        nn.init.normal_(self.conv_box.weight, mean=0, std=0.001)

    def forward(self, data_dict):
        spatial_features_2d = data_dict['spatial_features_2d']

        cls_preds = self.conv_cls(spatial_features_2d)
        box_preds = self.conv_box(spatial_features_2d)

        cls_preds = cls_preds.permute(0, 2, 3, 1).contiguous()  # [N, H, W, C]
        box_preds = box_preds.permute(0, 2, 3, 1).contiguous()  # [N, H, W, C]

        self.forward_ret_dict['cls_preds'] = cls_preds
        self.forward_ret_dict['box_preds'] = box_preds

        if self.conv_dir_cls is not None:
            dir_cls_preds = self.conv_dir_cls(spatial_features_2d)
            dir_cls_preds = dir_cls_preds.permute(0, 2, 3, 1).contiguous()
            self.forward_ret_dict['dir_cls_preds'] = dir_cls_preds
        else:
            dir_cls_preds = None

        if self.training:
            gt_boxes_enlarged = None
            if self.model_cfg.get('USE_MULTIFRAME_ENLARGED_GT_BOXES', False):
                from pcdet.utils.box_utils import boxes_to_corners_3d
                from pcdet.utils.common_utils import rotate_points_along_z

                batch_size, num_boxes, boxes_dim = data_dict['gt_boxes'].shape
                gt_boxes_enlarged = data_dict['gt_boxes'].clone()
                if num_boxes > 0:
                    stack_frame_size = data_dict['locations'].shape[2]
                    gt_boxes_corner = []
                    locations = data_dict['locations'].view(-1, stack_frame_size, 3)
                    rotations_y = data_dict['rotations_y'].view(-1, stack_frame_size)
                    gt_boxes = data_dict['gt_boxes'].view(-1, boxes_dim)
                    cur_gt_boxes = gt_boxes.clone()
                    for idx in range(stack_frame_size):
                        cur_gt_boxes[:, 0:3] = locations[:, idx, :]
                        cur_gt_boxes[:, -1] = rotations_y[:, idx]
                        gt_boxes_corner.append(boxes_to_corners_3d(cur_gt_boxes))
                    gt_boxes_corner = torch.cat(gt_boxes_corner, dim=1)
                    gt_boxes_corner -= gt_boxes[:, None, 0:3]
                    gt_boxes_corner_local = rotate_points_along_z(gt_boxes_corner, -gt_boxes[:, -2])
                    multi_length = gt_boxes_corner_local[:, :, 0].max(dim=1)[0] - gt_boxes_corner_local[:, :, 0].min(dim=1)[0]
                    multi_width = gt_boxes_corner_local[:, :, 1].max(dim=1)[0] - gt_boxes_corner_local[:, :, 1].min(dim=1)[0]
                    gt_boxes_enlarged = torch.cat(
                        [gt_boxes[:, 0:3], multi_length[:, None], multi_width[:, None], gt_boxes[:, 5:]],
                        dim=-1)
                    gt_boxes_enlarged = gt_boxes_enlarged.view(batch_size, num_boxes, boxes_dim)
                data_dict['gt_boxes_enlarged'] = gt_boxes_enlarged

            targets_dict = self.assign_targets(
                gt_boxes=data_dict['gt_boxes'], gt_boxes_enlarged=gt_boxes_enlarged
            )
            self.forward_ret_dict.update(targets_dict)

        if not self.training or self.predict_boxes_when_training:
            batch_cls_preds, batch_box_preds = self.generate_predicted_boxes(
                batch_size=data_dict['batch_size'],
                cls_preds=cls_preds, box_preds=box_preds, dir_cls_preds=dir_cls_preds
            )
            data_dict['batch_cls_preds'] = batch_cls_preds
            data_dict['batch_box_preds'] = batch_box_preds
            data_dict['cls_preds_normalized'] = False

        return data_dict