# coding: utf-8

"""
Класс для разбора параметров командной строки.
"""

import getopt
import os
import stocks3.share.messages as messages
import sys
import traceback
from abc import ABCMeta, abstractmethod

from stocks3.core.default import Default
from stocks3.core.exception import S3Exception, throw_only
# from stocks3.share.config import find_config_files, make_source_with_file

__author__ = "Zasimov Alexey"
__email__ = "zasimov-a@yandex-team.ru"


class S3LoadError(S3Exception):
    STAGE = "load"


class S3LoadXmlError(S3Exception):
    STAGE = "load_config"

    def __init__(self, filename, native_exception, block):
        self.filename = filename
        S3Exception.__init__(self, native_exception, block)


class Opts(object, metaclass=ABCMeta):
    DEFAULT_CLASS = Default

    DEFAULT_XML = "config/default-db.xml"

    OPTS_FORMAT = 's:vtc:i'
    EXT_OPTS = ["no-info"]

    def __init__(self, argv, additional_format=""):
        self.opts_format = self.OPTS_FORMAT + additional_format
        self.program_name = argv[0]
        self.argv = argv[1:]
        self.traceback = False
        self.default_xml = self.DEFAULT_XML
        self.user_sources_directory = None
        self.run_inactive = False
        self.args = self.parse_opts(self.argv)
        self.default = self.load_default_or_exit(self.default_xml)
        self._sources = None
        self._sources_is_loaded = False

    def _get_sources_directory(self):
        if self.user_sources_directory is not None:
            return self.user_sources_directory
        else:
            return self.default.sources_directory
    sources_directory = property(_get_sources_directory)

    def _get_sources(self):
        """
        Ленивое вычисление списка источников.
        """
        if not self._sources_is_loaded:
            self._sources = self.find_sources_or_exit()
            self._sources_is_loaded = True
        return self._sources
    sources = property(_get_sources)

    def parse_opts(self, argv):
        args, optlist = None, None
        try:
            optlist, args = getopt.getopt(argv, self.opts_format, self.EXT_OPTS)
        except getopt.GetoptError as err:
            print("Options error:", str(err))
            self.usage()
            exit(2)

        self.handle_opts(optlist)

        return args

    def handle_opts(self, optlist):
        unhandled = []
        for opt, value in optlist:
            if opt in ["-v"]:
                messages.verbose_flag = True
            elif opt in ["-t"]:
                self.traceback = True
            elif opt in ["-c"]:
                self.default_xml = value
            elif opt in ["-s"]:
                self.user_sources_directory = value
            elif opt in ["-i"]:
                self.run_inactive = True
            elif opt in ["--no-info"]:
                messages.info_flag = False
            else:
                # По этому списку будут бегать дочерние классы
                unhandled.append((opt, value))
        return unhandled

    def load_default_or_exit(self, default_xml):
        try:
            default = self.DEFAULT_CLASS(default_xml)
            return default
        except Exception as e:
            self.say("CONFIGERROR: default: %s\n" % e)
            exit(3)

    def say(self, message, force_kill_traceback=False):
        messages.error(message)
        if self.traceback and not force_kill_traceback:
            traceback.print_exc(file=sys.stderr)

    def _print_additional_info(self, e):
        if e.exc_info is None:
            return
        exc_type, exc_value, exc_traceback = e.exc_info
        log = traceback.format_tb(exc_traceback)
        self.say("\n*** Additional information ***\nIn class %s: %s\n%s\n*** End of additional information ***\n\n" %
                 (str(exc_type), exc_value, "".join(log)), True)

    def _print_stack(self, e, level=0):
        message = "***) %s %s" % (("--" * level)+"| ", e.__class__.__name__)
        if isinstance(e, S3Exception):
            message += " [%s]" % e.block.__class__.__name__
        else:
            message += ": "+str(e)
        print(message)
        if isinstance(e, S3Exception):
            self._print_additional_info(e)
            self._print_stack(e.nativeException, level+1)

    def say_exception(self, source_name, e):
        assert isinstance(e, S3Exception), "Expected S3Exception for say_exception."
        message = "ERROR: %s: %s: %s: %s\n" % (e.get_stage(), e.block.__class__.__name__, source_name, e)
        self.say(message, True)
        if self.traceback:
            print("*** Exceptions stack ***")
            self._print_stack(e)
            print("*** End of exceptions stack ***")

    def say_unknown_exception(self, source_name, e):
        message = "ERROR: %s: %s: %s: %s\n" % (e.__class__.__name__, "UNKNOWN", source_name, e)
        self.say(message)

    @throw_only(S3LoadError)
    def find_sources(self):
        if len(self.args) == 0:
            # Если имена источников не указаны в параетрах - обрабатываем все
            # источники.
            sources_files = find_config_files(self.sources_directory)
        else:
            # Иначе загружаем только указанные источники.
            sources_files = map(lambda x: x if x.endswith(".xml") else x+".xml", self.args)
        sources_files = map(lambda x: os.path.join(self.sources_directory, x), sources_files)

        sources = []
        for source_file in sources_files:
            try:
                source = make_source_with_file(source_file, self.default)
            except Exception as e:
                raise S3LoadXmlError(source_file, e, self)
            sources.append((source_file, source))

        return sources

    def find_sources_or_exit(self):
        try:
            return self.find_sources()
        except S3Exception as e:
            self.say_exception(e.nativeException.filename, e)
            exit(3)

    def get_active_sources(self):
        """
        Возвращает только активные источники.
        почему внизу [1] - потому что self.sources - tuple === (SourceName, source)
        """
        if self.run_inactive:
            return self.sources
        else:
            return filter(lambda source: source[1].active, self.sources)

    @abstractmethod
    def usage(self):
        """
        Этот метод должен быть переопределен.
        """
        pass
