# Copyright 2016-2017 Laszlo Attila Toth
# Distributed under the terms of the GNU Lesser General Public License v3

import os
import re
import time
import typing

from dewi.config.config import Config
from dewi.logparser.syslog import GenericParser, Parser
from dewi.module_framework.messages import CORE_CATEGORY, Level, Messages
from dewi.module_framework.module import GenericModule, Module


class LogParserModule(GenericModule):
    def get_registration(self) -> typing.List[typing.Dict[str, typing.Union[str, callable]]]:
        return []

    def start(self):
        pass

    def finish(self):
        pass


class _Pattern:
    def __init__(self, config: typing.Dict[str, typing.Union[str, callable]]):
        self.program = config.get('program', '')
        self.message_substring = config.get('message_substring', '')
        self.callback = config['callback']
        regex = config.get('message_regex', '')

        if regex:
            self.message_regex: typing.Pattern[str] = re.compile(regex)
            self.process = self.process_regex
        else:
            self.message_regex: typing.Pattern[str] = ''
            if self.message_substring:
                self.process = self.process_substring
            else:
                self.process = self.callback

    def process_regex(self, time, program, pid, msg):
        m = self.message_regex.match(msg)

        if m:
            self.callback(time, program, pid, msg)

    def process_substring(self, time, program, pid, msg):
        if self.message_substring in msg:
            self.callback(time, program, pid, msg)


class LogFileDefinition:
    def __init__(self, directory_list: typing.List[str], file_regex: typing.Union[typing.Pattern[str], str],
                 parser: Parser):
        self.directory_list = directory_list
        self.file_regex = file_regex
        self.parser = parser


class GenericLogFileDefinition(LogFileDefinition):
    def __init__(self):
        super().__init__(['/var/log'], r'^(syslog|system.log|messages)', GenericParser())


class LogHandlerModule(Module):
    """
    @type modules typing.List[LogParserModule]
    """

    def __init__(self, config: Config, messages: Messages, log_file_definition: LogFileDefinition):
        """
        base_path: which contains the directory of log messages
        It can be e.g. '/var'
        """
        super().__init__(config, messages)
        self._log_file_definition = log_file_definition
        self.modules = list()
        self._program_parsers = dict()
        self._other_parsers = set()

    def provide(self):
        return 'log'

    def register_module(self, m: type):
        self.modules.append(m(self._config, self._messages))

    def run(self):
        self._init_parsers()
        files = self._collect_files()
        self._process_files(files)
        self._finalize_parsers()

    def _init_parsers(self):
        for module in self.modules:
            registrations = module.get_registration()
            for reg in registrations:
                if 'program' in reg:
                    self._add_to_map(self._program_parsers, reg['program'], _Pattern(reg))
                else:
                    self._other_parsers.add(_Pattern(reg))
            module.start()

    @staticmethod
    def _add_to_map(dictionary, key, value):
        if key not in dictionary:
            dictionary[key] = set()

        dictionary[key].add(value)

    def _finalize_parsers(self):
        for module in self.modules:
            module.finish()

    def _collect_files(self):
        date_file_map = dict()
        log_dir: str = None

        for entry in self._log_file_definition.directory_list:
            if os.path.exists(entry):
                log_dir = entry
        files = os.listdir(log_dir)
        for file in files:
            if not re.match(self._log_file_definition.file_regex, file):
                continue

            filename = os.path.join(log_dir, file)
            with open(filename, encoding='UTF-8', errors='surrogateescape') as f:
                line = f.readline()
                parsed = self._log_file_definition.parser.parse_date(line)
                date_file_map[parsed.group('date')] = filename

        return [date_file_map[k] for k in sorted(date_file_map.keys())]

    def _process_files(self, files: typing.List[str]):
        start = time.clock()
        cnt = 0
        for fn in files:
            with open(fn, encoding='UTF-8', errors='surrogateescape') as f:
                cnt += self._process_file(f)

        end = time.clock()
        diff = end - start
        self.add_message(
            Level.DEBUG, CORE_CATEGORY, CORE_CATEGORY,
            "Run time: {} line(s) in {} s ({:.2f} kHz)".format(cnt, diff, cnt / diff / 1000))

    def _process_file(self, f):
        # This is a heavily optimized code, please consider the consequences before changing it
        # The loop body must be extremely fast, because it runs on every log line

        cnt = 0
        line = 'non-empty'
        while line:
            line = f.readline()
            cnt += 1
            parts = line.split(' ', 3)
            if len(parts) != 4:
                continue

            if '[' in parts[2]:
                program, pid = parts[2].split('[')
                pid = pid.split(']')[0]
            else:
                program, pid = parts[2][:-1], None

            if program in self._program_parsers:
                for module in self._program_parsers[program]:
                    module.process(parts[0], program, pid, parts[3])
            else:
                for module in self._other_parsers:
                    module.process(parts[0], program, pid, parts[3])

        return cnt
