from . import version
from ._version import __version__
from . import const

DAEMON_OP = ('STOP', 'PAUSE', 'EXEC')
DAEMON_MODE = ('AUTO', 'MANAGED', 'MANUAL')
DAEMON_STATUS = ('ENTER', 'MAIN', 'RUN', 'DONE', 'JUMP', 'REDIRECT', 'EDGE', 'INIT')
WORKER_STATUS = ('INIT', 'COMMAND', 'CONTINUE', 'DONE', 'JUMP', 'CERROR', 'ERROR')

############################################################
############################################################
# Guardian channel database definition
#
# This lists all channels that are used internally by the Guardian
# Daemon.
#
# It is also the database definition for the pcaspy EPICS portable
# channel access server:
#
#   https://pcaspy.readthedocs.org/en/latest/api.html#id1
#
# It lists all channels and their associated data types.

guarddb = {
    # request state
    # enum set at load time with states from system.
    'REQUEST': {
        'type': 'string',
        'guardstate': True,
        'writable': True,
    },
    # current state
    'STATE': {
        'type': 'string',
        'guardstate': True,
    },
    # target state
    'TARGET': {
        'type': 'string',
        'guardstate': True,
    },
    # nominal state
    'NOMINAL': {
        'type': 'string',
        'guardstate': True,
    },

    # op mode
    'OP': {
        'type': 'enum',
        'enums': DAEMON_OP,
        'writable': True,
        'archive': True,
    },
    # run mode
    'MODE': {
        'type': 'enum',
        'enums': DAEMON_MODE,
        'writable': True,
        'archive': True,
    },

    # load request
    'LOAD': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'writable': True,
    },
    'LOAD_STATUS': {
        'type': 'enum',
        'enums': ('DONE', 'REQUEST', 'SET', 'INPROGRESS', 'ERROR'),
        'archive': True,
    },

    # management
    'MANAGER': {
        'type': 'string',
        'value': '',
        'writable': True,
    },
    'STALLED': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },

    # code execution status
    'STATUS': {
        'type': 'enum',
        'enums': DAEMON_STATUS,
        'archive': True,
    },
    # worker return status
    'WORKER': {
        'type': 'enum',
        'enums': WORKER_STATUS,
    },
    # worker execution time and cycles
    'EXECTIME': {
        'type': 'float',
        'unit': 'seconds',
        'value': -1,
        'prec': 4,
        'lolo': -1,
        'low': -1,
        'high': 1./const.CPS,
        'hihi': 1,
        'log_changes': False,
        'archive': True,
    },
    'EXECCYCLES': {
        'type': 'int',
        'value': -1,
        'lolo': -1,
        'low': -1,
        'high': 1,
        'hihi': const.CPS,
        'log_changes': False,
    },
    'EXECTIME_LAST': {
        'type': 'float',
        'unit': 'seconds',
        'value': -1,
        'prec': 4,
        'lolo': -1,
        'low': -1,
        'high': 1./const.CPS,
        'hihi': 1,
        'log_changes': False,
    },
    'EXECCYCLES_LAST': {
        'type': 'int',
        'value': -1,
        'lolo': -1,
        'low': -1,
        'high': 1,
        'hihi': const.CPS,
        'log_changes': False,
    },
    'LOOPUSEC': {
        'type': 'int',
        'log_changes': False,
    },
    'MAINUSEC': {
        'type': 'int',
        'log_changes': False,
    },

    # error flag
    'ERROR': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },
    # connection status
    'CONNECT': {
        'type': 'enum',
        'enums': ('OK', 'ERROR'),
        'archive': True,
    },

    'PV_TOTAL': {
        'type': 'int',
        'value': -1,
        'archive': True,
    },

    # manager subordinate node count
    'SUBNODES_TOTAL': {
        'type': 'int',
        'value': -1,
    },
    'SUBNODES_NOT_OK': {
        'type': 'int',
        'value': -1,
        'low': -1,
        'high': 1,
    },

    # daemon message
    'GRDMSG': {
        'type': 'char',
        'count': 100,
        'value': [0],
    },
    # user message
    'USERMSG': {
        'type': 'char',
        'count': 100,
        'value': [0],
    },
    # notification flag (USERMSG present)
    'NOTIFICATION': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },
    # operational summary bit (OP/MODE/ERROR status nominal)
    'ACTIVE': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },
    # subnode summary bit (all sub nodes OK)
    'READY': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },
    # intent summary bit (REQUEST == NOMINAL)
    'INTENT': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },
    # overall summary bit (READY + INTENT + NOMINAL)
    'OK': {
        'type': 'enum',
        'enums': ('False', 'True'),
        'guardbool': True,
        'archive': True,
    },
    # log level
    'LOGLEVEL': {
        'type': 'enum',
        'enums': ('INFO', 'DEBUG', 'LOOP'),
        'writable': True,
    },
    # daemon initialization time, in UNIX seconds
    'TIME_INIT': {
        'type': 'int',
    },
    # uptime
    'TIME_UP': {
        'type': 'int',
        'unit': 'seconds',
        'log_changes': False,
    },
    # code archive id
    'ARCHIVE_ID': {
        'type': 'int',
        'archive': True,
    },
    # system hostname
    'HOSTNAME': {
        'type': 'str',
        'value': const.HOSTNAME,
    },
    # guardian version
    'VERSION': {
        'type': 'int',
        'value': version.vers_str2int(__version__),
        'archive': True,
    },
    'VERSION_S': {
        'type': 'str',
        'value': __version__,
    },
}

# add the guardstate readback channels
# each channel marked 'guardstate' will have corresponding _S string
# and _N numeric channels added.  These will be updated according to
# a states:index dictionary
for channel, data in [tup for tup in guarddb.items()]:
    if data.get('guardstate', False):
        guarddb[channel+'_S'] = {
            'type': 'string',
        }
        guarddb[channel+'_N'] = {
            'type': 'int',
            'archive': True,
        }

# first 10 user messages
for i in range(const.USERMSG_COUNT):
    guarddb['USERMSG'+str(i)] = {
        'type': 'char',
        'count': const.USERMSG_STRING_LENGTH,
        'value': [0],
    }

# add setpoint monitor channels
# whether monitoring is enabled
guarddb['SPM_MONITOR'] = {
    'type': 'enum',
    'enums': ('False', 'True'),
    'guardbool': True,
    'archive': True,
}
guarddb['SPM_MONITOR_NOTIFY'] = {
    'type': 'enum',
    'enums': ('False', 'True'),
    'guardbool': True,
}
# total number of setpoints
guarddb['SPM_TOTAL'] = {
    'type': 'int',
    'value': -1,
    'archive': True,
}
# number of setpoints that have changed
guarddb['SPM_CHANGED'] = {
    'type': 'int',
    'value': -1,
    'lolo': -1,
    'low': -1,
    'high': 1,
    'hihi': 1,
    'archive': True,
}
# first 10 setpoint differences
for i in range(const.SPM_DIFF_COUNT):
    # channel name
    guarddb['SPM_DIFF'+str(i)] = {
        'type': 'char',
        'count': const.CHANNEL_NAME_LENGTH,
        'value': [0],
    }
    for t in ['S', 'C', 'D']:
        # setpoint value, string representation
        guarddb['SPM_DIFF'+str(i)+'_'+t] = {
            'type': 'string',
            'value': '',
        }
# SPM snapshot
guarddb['SPM_SNAP'] = {
    'type': 'enum',
    'enums': ('False', 'True'),
    'guardbool': True,
    'writable': True,
}

############################################################
############################################################

class Database(object):
    def __init__(self, cas):
        self._cas = cas
        self._db = {}
        self._writable = []
        for channel, entry in guarddb.items():
            self._db[channel] = None
            if entry.get('writable', False):
                self._writable.append(channel)
            if 'value' in entry:
                self._db[channel] = entry['value']
            elif entry['type'] == 'enum' and entry['enums']:
                # zeroth value of enum is always default
                value = entry['enums'][0]
                if entry.get('guardbool', False):
                    value = eval(value)
                self._db[channel] = value
        self.request_event = False

    ########################################

    def __getitem__(self, channel):
        return self._db[channel]

    def __setitem__(self, channel, value):
        self._db[channel] = value
        if self._cas:
            self._cas[channel] = value

    ########################################
    # CAS interaction

    def update(self):
        """Update the db from the cas.

        """
        if not self._cas:
            return

        if self._cas._request_event.is_set():
            self.request_event = True
            self._cas._request_event.clear()

        for channel in self._writable:
            self._db[channel] = self._cas[channel]


    def update_system(self, system, init=False):
        """Update system definition

        E.g. state index and request state enum

        """
        if not self._cas:
            return
        index = system.state_index_dict()
        # FIXME: this truncation should throw a WARNING
        enum = system.request_states[:const.REQUEST_STATE_LIMIT]
        # always add NONE state
        index['NONE'] = dict(const.RESERVED_STATES)['NONE']
        # enum += ('NONE',)
        self._cas.update_state_index(index, init=init)
        return self._cas.update_request_enum(enum, init=init)
