import os
import sys
import signal
import argparse
if sys.version_info.major > 2:
    import builtins
else:
    import __builtin__ as builtins

from . import db
from . import cli
from . import const
from .log import get_logger

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

PROG = 'guardian'
description = '''Advanced LIGO Guardian state machine engine.'''
usage = '''
guardian [<options>] [-i <system>]                 interactive shell
guardian [<options>] <system>                      state machine daemon
guardian [<options>] <system> <state>              single state execution
guardian [<options>] <system> <state> <request>    single path execution
'''
fulldesc = """
Guardian is a state machine traversal engine.  State machines are
described by system description modules.  See the following link for a
full description of Guardian, including module syntax, and state
machine behavior:

  https://awiki.ligo-wa.caltech.edu/aLIGO/Guardian

If no system is specified or the interactive flag is provided the
interactive interpreter will be launched.  This is useful for testing
commands, including interactive use of the Ezca EPICS interface.

Systems can be specified either by full path to the system description
module, or by name (<NAME>).  If provided as path, the path basename
(file name minus '.py' extension) will be used as the system name.  If
specified by name, guardian will search for the appropriate module in
GUARD_MODULE_PATH, which defaults to:

  <USERAPPS_DIR>/*/{<ifo>,common}/guardian.

When a single system argument is provided the state machine engine
daemon is launched, which immediately begins executing the state graph
described in the module.  Control and status of the daemon is provided
via EPICS channels with the prefix '<IFO>:GRD_<NAME>_'.

Additional arguments are interpreted as system state names.  If a
single state name is supplied, the single state will be executed to
completion, after which the program will exit.  If two states are
specified the first will be interpreted as initial state and the
second as requested state.  Guardian will attempt to reach the
requested state by executing the shortest path through the graph.
Upon completion of the requested state the program will exit.

Ctrl-C can be used to stop execution at any point.

The --name and --ca-prefix options can be used to override the system
name and prefix specifications.  Prefixes are combined with the IFO
environment variable to create a proper LIGO channel name prefix that
will be appended to all EPICS channel access calls via the ezca
object (see Ezca for more info).

Environment variables:
  IFO                       IFO designator (required)
  USERAPPS_DIR              LIGO USERAPPS root path [/opt/rtcds/userapps/release]
  GUARD_MODULE_PATH         Override default userapps system module search path
  GUARD_ARCHIVE_ROOT        Set system archive root directory, enable code archiving
  GUARD_LOG_LEVEL           Log level: WARNING, [INFO], DEBUG, LOOP
  GUARD_CPS                 Main loop cycles per seconds [16]
  EZCA_TIMEOUT              Channel access connection timeout in seconds [2]
  EZCA_CA                   Channel access enable/disable [TRUE]
  EPICS_CAS_INTF_ADDR_LIST  Network interface for GRD interface
  EPICS_CAS_SERVER_PORT     Network port for GRD interface"""

parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    prog=PROG,
    usage=usage,
    )

cli.parser_add_system(parser)
parser.add_argument('state', metavar='<state>', type=str, nargs='?',
                    help="initial state")
parser.add_argument('request', metavar='<request>', type=str, nargs='?',
                    help="request state")

parser.add_argument('-ca', '--prefix', '--ca-prefix', metavar='<prefix>',
                    type=str, dest='ca_prefix',
                    help="system EPICS channel access prefix ")
parser.add_argument('-s', '--state', metavar='<state>', dest='dstate',
                    help="set initial state for daemon")
parser.add_argument('-r', '--request', metavar='<state>', dest='drequest',
                    help="set initial request state for daemon")
parser.add_argument('-o', '--op', metavar='<op>', dest='dop',
                    choices=db.DAEMON_OP, default='EXEC',
                    help="set initial daemon OP ({}|{})".format(*db.DAEMON_OP))
parser.add_argument('-m', '--mode', metavar='<mode>', dest='dmode',
                    choices=db.DAEMON_MODE,
                    help="set initial daemon MODE ({}|{}|{})".format(*db.DAEMON_MODE))

cgroup = parser.add_mutually_exclusive_group()
cgroup.add_argument('-i', '--interactive', action='store_true',
                    help="launch interactive guardian shell")
cgroup.add_argument('-p', '--print', action='store_const', dest='cmd', const='print',
                    help="print system information and exit")
cgroup.add_argument('-c', '--channels', action='store_const', dest='cmd', const='channels',
                    help="print daemon CAS channels (archive channels marked with '*')")

cli.parser_add_version(parser)
parser.add_argument('--usage', action='store_true',
                    help="show more detailed usage and exit")

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

def main():

    args = parser.parse_args()

    if args.usage:
        print(description)
        print(fulldesc)
        sys.exit()

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

    system = None
    if args.system:
        system = cli.init_system(args)

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

    if args.cmd == 'channels':
        ifo = '<IFO>'
        name = '<NODE>'
        if system is not None:
            if system.ifo:
                ifo = system.ifo
            name = system.name
        for chan in sorted(db.guarddb):
            channel = const.CAS_PREFIX_FMT.format(IFO=ifo, SYSTEM=name) + chan
            flag = ''
            if db.guarddb[chan].get('archive'):
                flag = '*'
            print('{} {}'.format(channel, flag))
        sys.exit()

    if args.cmd == 'print':
        if system is None:
            sys.exit("Must specify system to print.")
        cli.print_system(system)
        sys.exit()
    elif system is not None:
        system.load()

    if args.interactive or not args.system:
        import IPython

        from .state import GuardState, GuardStateDecorator
        from .manager import Node, NodeManager

        from ezca import Ezca, LIGOFilter, LIGOFilterManager

        banner = """
--------------------
aLIGO Guardian Shell
--------------------
""".lstrip()

        builtins._IFO_ = const.IFO

        user_ns = {
            'IFO': _IFO_,
            '_IFO_': _IFO_,
            'LIGOFilter': LIGOFilter,
            'LIGOFilterManager': LIGOFilterManager,
            'GuardState': GuardState,
            'GuardStateDecorator': GuardStateDecorator,
            'Node': Node,
            'NodeManager': NodeManager,
        }

        from .ligopath import userapps_guardian_paths
        sys.path += userapps_guardian_paths()

        if system:
            builtins._SYSTEM_ = system.name
            builtins.ezca = Ezca(prefix=system.ca_prefix)
        else:
            builtins._SYSTEM_ = const.HOSTNAME
            builtins.ezca = Ezca(prefix=args.ca_prefix)

        user_ns['SYSTEM'] = _SYSTEM_
        user_ns['_SYSTEM_'] = _SYSTEM_

        user_ns['ezca'] = ezca
        if ezca.prefix:
            banner += "ezca prefix: %s\n" % ezca.prefix

        if system:
            banner += "system: %s (%s)\n" % (system.name, system.path)
            user_ns[system.name] = system
            user_module = system.module
            user_module.__dict__.update(user_ns)
            user_ns = None
            if system.node_managers:
                banner += "node managers ('<name>.init()' to initialize):\n"
                for name, obj in system._node_managers.items():
                    banner += '  {} = {!r}\n'.format(name, obj)
        else:
            user_module = None

        def notify(msg):
            print('USERMSG: {}'.format(msg))
        def log(msg):
            print(msg)

        builtins.notify = notify
        builtins.log = log

        IPython.embed(banner1=banner, user_module=user_module, user_ns=user_ns)
        sys.exit()

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

    try:
        from .daemon import Daemon
    except ImportError as e:
        sys.exit("Guardian daemon not supported on this system: %s" % e)

    single_shot = False
    loglevel = 'INFO'

    if args.state:
        single_shot = True

        if not args.request:
            args.request = args.state

    # validate states if specified
    initial_state = args.state or args.dstate
    initial_request = args.request or args.drequest
    for state in initial_state, initial_request:
        if state and state not in system:
            sys.exit("State '%s' not defined in system %s." % (state, system.name))

    # specify archive if not running single shot
    if os.getenv('GUARD_ARCHIVE_ROOT', None) and not single_shot:
        from .archive import SystemArchiveGit
        archive_dir = os.path.join(os.getenv('GUARD_ARCHIVE_ROOT'), system.name)
        archive = SystemArchiveGit(archive_dir)
    else:
        archive = None

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

    logger = get_logger(system.name, level=loglevel)

    guard = Daemon(system, logger,
                   initial_op=args.dop,
                   initial_mode=args.dmode,
                   initial_state=args.state or args.dstate,
                   initial_request=args.request or args.drequest,
                   single_shot=single_shot,
                   archive=archive,
                   )

    # set TERM signal handler
    # FIXME: this shouldn't be necessary since daemon=True should
    # take care of this
    def sigterm(signum, frame):
        sys.stderr.write("caught signal %s; terminating\n" % signum)
        raise SystemExit
    signal.signal(signal.SIGTERM, sigterm)

    try:
        guard.run()
    except KeyboardInterrupt:
        sys.stderr.write("caught keyboard interrupt; terminating\n")
    finally:
        guard.stop()



if __name__ == '__main__':
    main()
