#!/usr/bin/env python3
"""
Generate supervisord configuration files.

Usage:
    felix-supervisord-generate [options] <felix-config-file>

Options:
    -t, --template <supervisord-template>   Supervisord template [default: supervisord.conf.jinja]
    -d, --destination DIRECTORY             Directory for the generates SUpervisor config file [default: /det/tdaq/felix-operation/supervisord/]
    -c, --config-dir DIRECTORY              Directory with the configuration files (elinkconfig) [default: /det/tdaq/felix-operation/config]
    -b, --bus-dir DIRECTORY                 Directory for the bus [default: /det/tdaq/felix-operation/bus]
    -m, --tmp-dir DIRECTORY                 Absolute directory for the temporary files of supervisord [default: /dev/shm]
    -g, --bus-groupname GROUP_NAME          Group name to use for the bus [default: FELIX]
    -l, --log-dir DIRECTORY                 Directory for the logfiles [default: /logs/felix-operation]
    -u, --user USER                         User of supervisor HTTP interface [default: username]
    -p, --password PWD                      Password of supervisor HTTP interface [default: password]
    -q, --tdaq RELEASE                      TDAQ release for felix2atlas and felix-supervisor [default: tdaq-11-02-00].
    --tdaq-env                              Generates TDAQ environment for felix-supervisor
    --error-out FIFO                        FIFO for errors [default: /dev/shm/felix-errors.json]
    --stats-out FIFO                        FIFO for stats [default: /dev/shm/felix-statistics.json]
    --error-port PORT                       PORT for errors [default: 53401]
    --stats-port PORT                       PORT for stats [default: 53400]
    --dcs-watermark SIZE                    DCS NetIO watermark in Bytes [default: 972]
    --dcs-unbuffered                        Use unbuffered DCS socket
    -v, --verbose                           Verbose output

Arguments:
    <felix-config-file>         FELIX configuration file
"""

from felixtag import FELIX_TAG

from docopt import docopt  # noqa: E402
from jinja2 import Environment, FileSystemLoader, select_autoescape
import subprocess
import os
import re
import sys
import logging
import json

# add the ../python path to find felix python libs
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'python'))


class tdaq_env():

    def __init__(self, release="tdaq-11-02-00"):
        """
        Retrieve the environment (PYTHONPATH, LD_LIBRARY_PATH etc) set up by the tdaq release,
        retain and test only the necessary for ERS.

        Returns:
            string for felix-supervisor environment in Supervisor config file.
        """
        self.release = release
        self.env = {}
        self.get_tdaq_env()
        self.filter_pythonpath()
        self.filter_ldlibrary()
        self.test_result = self.test()

    def get_tdaq_env(self):
        logging.info("Retrieving tdaq release environment...")
        cmd = ("source /sw/tdaq/setup/setup_" + self.release + ".sh > /dev/null 2>&1; " +
               "echo $PYTHONPATH; echo $LD_LIBRARY_PATH; echo $TDAQ_IPC_INIT_REF")
        logging.debug("using command: %s", cmd)
        stdout = subprocess.check_output(cmd, shell=True,  env={})
        rawenv = stdout.decode('utf-8').split('\n')
        self.env['PYTHONPATH'] = rawenv[0].rstrip(':')
        self.env['LD_LIBRARY_PATH'] = rawenv[1].rstrip(':')
        self.env['TDAQ_IPC_INIT_REF'] = '"'+rawenv[2]+'"'
        self.env['TDAQ_IPC_INIT_REF'] = self.env['TDAQ_IPC_INIT_REF'].replace('%', '%%')

    def check(self, e, veto):
        for v in veto:
            if v in e:
                return False
        return True

    def filter_pythonpath(self):
        veto = ['external', 'ROOT']
        ppath = self.env['PYTHONPATH'].split(':')
        self.env['PYTHONPATH'] = os.pathsep.join([e for e in ppath if self.check(e, veto)])

    def filter_ldlibrary(self):
        veto = ['external', 'ROOT', 'libsodium', 'zeromq', 'protobuf', 'sqlite', 'XercesC', 'png',
                'CppUnit', 'libffi', 'xslt', 'xml', 'GSL', 'lz4', 'qt', 'zlib', 'binutils',
                'gcc/', 'tbb', 'vdt', 'xrootd', 'pcre', 'current', 'freetype', 'fontconfig', 'xkb',
                'oracle', 'sql', 'freetype']
        lpath = self.env['LD_LIBRARY_PATH'].split(':')
        self.env['LD_LIBRARY_PATH'] = os.pathsep.join([e for e in lpath if self.check(e, veto)])

    def test(self):
        logging.info("Testing filtered tdaq environment...")
        f = open("tdaq_ers_test.py", "w")
        f.write("import ers\n")
        f.write("from ipc import IPCPartition\n")
        f.write("IPCPartition('initital')\n")
        f.close()
        cmd = ("source /det/tdaq/felix-operation/scripts/setup_FELIX.sh \n " +
               "export PYTHONPATH=" + self.env['PYTHONPATH'] + ":$PYTHONPATH\n" +
               "export LD_LIBRARY_PATH=" + self.env['LD_LIBRARY_PATH'] + "$LD_LIBRARY_PATH\n" +
               "export TDAQ_IPC_INIT_REF=" + self.env['TDAQ_IPC_INIT_REF'] + "\n" +
               "python tdaq_ers_test.py")
        try:
            subprocess.check_output(cmd, shell=True)
        except subprocess.CalledProcessError as exc:
            logging.error("Test failed, not returning tdaq environement. Error %d %s", exc.returncode, exc.output)
            os.system("rm tdaq_ers_test.py")
            return 0
        logging.info("Test of tdaq environment passed.")
        os.system("rm tdaq_ers_test.py")
        return 1

    def get_env(self):
        if self.test_result:
            return ("TDAQ_IPC_INIT_REF=" + self.env['TDAQ_IPC_INIT_REF'] + "," +
                    "PYTHONPATH=" + self.env['PYTHONPATH'] + ":%(ENV_PYTHONPATH)s," +
                    "LD_LIBRARY_PATH=" + self.env['LD_LIBRARY_PATH'] + ":%(ENV_LD_LIBRARY_PATH)s")
        else:
            return "LD_LIBRARY_PATH=%(ENV_LD_LIBRARY_PATH)s, PYTHONPATH=%(ENV_PYTHONPATH)s"


class supervisor_generate():

    def __init__(self, opt):
        self.opt = opt
        self.generate()

    def read_config(self, config_file):
        """
        Read the felix config file.
        With following format: host_name device direction mode interface detectorid connectorid elinkconfig dma_id

        Returns:
            dictionary with keys: host_name, direction, device and values: dictionary with mode, iface, did, cid and elink_config
        """
        comment = r'^([^#]*)#(.*)$'
        sformat = r'^(?P<host_name>[^\s]+)\s+(?P<direction>[^\s]+)\s+(?P<device>[0-9A-Fa-f]+)\s+(?P<mode>[^\s]+)\s+(?P<clock>[^\s]+)\s+(?P<ifaces>[^\s]+)\s+(?P<did>[0-9A-Fa-fXx]+)\s+(?P<cid>[0-9A-Fa-fXx]+)\s+(?P<elink_config>[^\s]+)\s+(?P<dma>-?\d+)$'
        line_no = 0
        syntax_errors = False
        config = {}
        with open(config_file) as fp:
            line = fp.readline()
            line_no += 1
            while line:
                line = line.strip()
                m = re.match(comment, line)
                if m:  # line contains a # comment
                    line = m.group(1)
                if len(line) > 0:
                    m = re.match(sformat, line)
                    if m:
                        for idx, iface in enumerate(m.group('ifaces').split(',')):
                            hostname = m.group('host_name').strip()
                            direction = m.group('direction').strip()
                            device = int(m.group('device'))
                            dma = m.group('dma').strip()
                            mode = m.group('mode')
                            did = m.group('did')
                            cid = m.group('cid')
                            values = {
                                'mode': mode.strip(),
                                'iface': iface.strip(),
                                'direction': direction,
                                'clock': m.group('clock').strip(),
                                'dma': int(dma),
                                'did': int(did, 16) if did.startswith('0x') or did.startswith('0X') else int(did),
                                'cid': int(cid, 16) if cid.startswith('0x') or cid.startswith('0X') else int(cid),
                                'elink_config': m.group('elink_config').strip(),
                                'card': int(int(device) / 2)
                            }
                            if hostname in config.keys():
                                if idx in config[hostname]:
                                    if device in config[hostname][idx].keys():
                                        logging.warning("duplicate entry for device %d of host %s", device, hostname)
                                        syntax_errors = True
                            else:
                                config[hostname] = {}
                            if idx not in config[hostname].keys():
                                config[hostname][idx] = {'iface': iface}
                                config[hostname][idx]['devices'] = {}
                            config[hostname][idx]['devices'][device] = values
                    else:
                        logging.error("Syntax error in line number: %d \n line %s", line_no, line)
                        syntax_errors = True
                line = fp.readline()
                line_no += 1
        if syntax_errors:
            exit(1)
        return config

    def generate(self):
        """
        Generate Supervisor config file
        """
        template_file = self.opt['--template']
        template_search_dir = os.path.dirname(template_file)
        config_file = self.opt['<felix-config-file>']
        config_search_dir = os.path.dirname(config_file)

        env = Environment(
            loader=FileSystemLoader([".", config_search_dir, template_search_dir]),
            autoescape=select_autoescape()
        )
        # get template and read confil file
        logging.info("Loading template %s", template_file)
        template = env.get_template(template_file)
        logging.info("Loading config %s", config_file)
        config = self.read_config(config_file)

        # print(json.dumps(config, indent=4))

        tenv = "LD_LIBRARY_PATH=%(ENV_LD_LIBRARY_PATH)s, PYTHONPATH=%(ENV_PYTHONPATH)s"
        if self.opt['--tdaq-env']:
            tdaq = tdaq_env(release=self.opt['--tdaq'])
            tenv = tdaq.get_env()
            logging.debug("tdaq environment: %s", tenv)

        for hostname in config.keys():
            output_filename = os.path.join(args['--destination'], "supervisord_" + hostname + ".conf")
            logging.info("Producing %s", output_filename)

            disable_daq = True
            # disable daq if all devices have direction set to none
            # meant to felix-register and felix2atlas
            for d in config[hostname][0]['devices']:
                disable_daq = (disable_daq and True) if config[hostname][0]['devices'][d]['direction'] == 'none' else (disable_daq and False)

            data = {
                "bus_dir": self.opt["--bus-dir"],
                "bus_groupname": self.opt["--bus-groupname"],
                "dcs_watermark": int(self.opt['--dcs-watermark']),
                "dcs_unbuffered": bool(self.opt['--dcs-unbuffered']),
                "error_out": self.opt['--error-out'],
                "error_port": self.opt['--error-port'],
                "stats_out": self.opt['--stats-out'],
                "stats_port": int(self.opt['--stats-port']),
                "hostname": hostname,
                "devices": config[hostname][0]['devices'],
                "ifaces": config[hostname],
                "config_dir": self.opt['--config-dir'],
                "log_dir": self.opt['--log-dir'],
                "tmp_dir": self.opt['--tmp-dir'],
                "username": args['--user'],
                "password": args['--password'],
                "tdaq_release": args['--tdaq'],
                "tdaq_environment": tenv,
                "config_file": output_filename,
                "disable_daq": disable_daq,
            }
            logging.debug("Data passed to Jinja:\n %s", json.dumps(data, indent=4, sort_keys=True))

            output = template.render(data)
            with open(output_filename, "w") as output_file:
                output_file.write(output)


if __name__ == "__main__":
    args = docopt(__doc__, version=sys.argv[0] + " " + FELIX_TAG)
    lvl = logging.INFO
    if args['--verbose']:
        lvl = logging.DEBUG
    logging.basicConfig(level=lvl, format='[%(asctime)s] %(levelname)s: %(message)s')
    sg = supervisor_generate(args)

    """
    Data format passed to Jinja template:
    {
        "bus_dir": "/det/tdaq/felix-operation/bus",
        "bus_groupname": "FELIX",
        "config_dir": "/det/tdaq/felix-operation/config",
        "config_file": "./supervisord_pc-tbed-felix-06.cern.ch.conf",
        "dcs_unbuffered": false,
        "dcs_watermark": 972,
        // To be used for a single interface
        "devices": {
            "0": {
                "card": 0,
                "cid": 0,
                "did": 6,
                "direction": "none",
                "dma": -1,
                "elink_config": "pc-tbed-felix-06-d0.jelc",
                "iface": "priv0",
                "mode": "full"
            },
            "1": {
                "card": 0,
                "cid": 1,
                "did": 6,
                "direction": "none",
                "dma": -1,
                "elink_config": "pc-tbed-felix-06-d1.jelc",
                "iface": "priv0",
                "mode": "full"
            }
        },
        // To be used for multiple interfaces
        "ifaces": {
            "0": {
                "devices": {
                    "0": {
                        "card": 0,
                        "cid": 0,
                        "did": 6,
                        "direction": "none",
                        "dma": -1,
                        "elink_config": "pc-tbed-felix-06-d0.jelc",
                        "iface": "priv0",
                        "mode": "full"
                    },
                    "1": {
                        "card": 0,
                        "cid": 1,
                        "did": 6,
                        "direction": "none",
                        "dma": -1,
                        "elink_config": "pc-tbed-felix-06-d1.jelc",
                        "iface": "priv0",
                        "mode": "full"
                    }
                }
            },
            "1": {
                "devices": {
                    "0": {
                        "card": 0,
                        "cid": 0,
                        "did": 6,
                        "direction": "none",
                        "dma": -1,
                        "elink_config": "pc-tbed-felix-06-d0.jelc",
                        "iface": "priv0",
                        "mode": "full"
                    },
                    "1": {
                        "card": 0,
                        "cid": 1,
                        "did": 6,
                        "direction": "none",
                        "dma": -1,
                        "elink_config": "pc-tbed-felix-06-d1.jelc",
                        "iface": "priv0",
                        "mode": "full"
                    }
                }
            }
        }
        "disable_daq": true,
        "error_elink": "0x1100",
        "error_out": "/dev/shm/felix-errors.json",
        "error_port": "53401",
        "hostname": "pc-tbed-felix-06.cern.ch",
        "log_dir": "/logs/felix-operation",
        "password": "password",
        "stats_elink": "0x1000",
        "stats_out": "/dev/shm/felix-statistics.json",
        "stats_port": 53400,
        "tdaq_env": "LD_LIBRARY_PATH=%(ENV_PYTHONPATH)s, PYTHONPATH=%(ENV_LD_LIBRARY_PATH)s",
        "tdaq_release": "tdaq-11-02-00",
        "tmp_dir": "/dev/shm",
        "username": "username"
    }
    """
