# -*- coding: utf-8 -*-
"""
Module for providing various distutil command that integrates with
setuptools, and for setting up the build environments and building
JavaScript artifacts.
"""

from __future__ import absolute_import

import sys
import logging

from distutils.errors import DistutilsModuleError
from distutils.core import Command
from distutils import log

from calmjs.ui import prompt_overwrite_json


class DistutilsLogHandler(logging.Handler):
    """
    A handler that streams the logs to the distutils logger.
    """

    def __init__(self, distutils_log=log):
        logging.Handler.__init__(self)
        self.log = distutils_log
        # Basic numeric table
        self.level_table = {
            logging.CRITICAL: distutils_log.FATAL,
            logging.ERROR: distutils_log.ERROR,
            logging.WARNING: distutils_log.WARN,
            logging.INFO: distutils_log.INFO,
            logging.DEBUG: distutils_log.DEBUG,
        }
        self.setFormatter(logging.Formatter('%(message)s'))

    def _to_distutils_level(self, level):
        return self.level_table.get(level, level // 10)

    def emit(self, record):
        level = self._to_distutils_level(record.levelno)
        try:
            msg = self.format(record)
            self.log.log(level, msg)
        except Exception:  # pragma: no cover
            # LogRecord.__str__ shouldn't fail... probably.
            self.log.warn('Failed to convert %s' % record)


distutils_log_handler = DistutilsLogHandler()


def use_distutils_logger(logger_ids=('calmjs',)):
    def decorator(method):
        def run(cmd):
            root_logger = logging.getLogger()
            old_level = root_logger.level
            root_logger.setLevel(logging.DEBUG)

            for logger_id in logger_ids:
                logger = logging.getLogger(logger_id)
                logger.addHandler(distutils_log_handler)

            try:
                method(cmd)
            finally:
                # Remove the logging handlers and restore the level.
                for logger_id in logger_ids:
                    logger = logging.getLogger(logger_id)
                    logger.removeHandler(distutils_log_handler)

                root_logger.setLevel(old_level)

        return run
    return decorator


class PackageManagerCommand(Command):
    """
    Simple compatibility hook for a package manager runtime.
    """

    # subclasses need to define these
    runtime = None
    # description = "base command for package manager compatibility helper"

    indent = 4

    @classmethod
    def _initialize_user_options(cls):
        cls.user_options = []
        for full, short, desc in cls.runtime.pkg_manager_options:
            if short is None:
                cls.user_options.append((full, short, 'action: ' + desc))
            else:
                cls.user_options.append((full, short, desc))

    # keywords that are actions that result in effects that we support
    # TODO derive this like how the runtime does.
    actions = ('view', 'init', 'install')

    def _opt_keys(self):
        for opt in self.user_options:
            yield opt[0]

    def initialize_options(self):
        for key in self._opt_keys():
            setattr(self, key, False)
        self.stream = None  # extra output
        self.callback = None

    def do_view(self):
        pkg_name = self.distribution.get_name()
        self.cli_driver.pkg_manager_view(pkg_name, stream=self.stream)

    def do_init(self):
        pkg_name = self.distribution.get_name()
        self.cli_driver.pkg_manager_init(
            pkg_name,
            overwrite=self.overwrite, merge=self.merge,
            callback=self.callback,
            stream=self.stream,
        )

    def do_install(self):
        pkg_name = self.distribution.get_name()
        self.cli_driver.pkg_manager_install(
            pkg_name,
            overwrite=self.overwrite, merge=self.merge,
            callback=self.callback,
            production=self.production, development=self.development,
            stream=self.stream,
        )

    def finalize_options(self):
        opts = [i for i in (getattr(self, k) for k in self.actions) if i]
        if not opts:
            # default to view
            self.view = True
        if self.view or self.dry_run:
            self.stream = sys.stdout
        self.callback = prompt_overwrite_json if self.interactive else None
        # require explicit boolean value.
        self.production = True if self.production else None
        self.development = True if self.development else None

    @use_distutils_logger()
    def run(self):
        if self.dry_run:
            # Do the default action and finish, as everything else may
            # cause permanent changes.
            self.do_view()
            return
        self.run_command('egg_info')
        if self.install:
            self.do_install()
        elif self.init:
            self.do_init()
        elif self.view:
            self.do_view()


class BuildArtifactCommand(Command):
    """
    Command for building artifacts for the given package.
    """

    user_options = []
    artifact_builder = None

    def initialize_options(self):
        """
        Implement items that could go into the spec, such as setting of
        build dir prefix.
        """

    def finalize_options(self):
        """
        If finalization is needed.
        """

    @use_distutils_logger()
    def run(self):
        if not callable(self.artifact_builder):
            raise DistutilsModuleError(
                "artifact_builder is not callable for %s" % self)
        if self.dry_run:
            return
        package_name = self.distribution.get_name()
        if not self.artifact_builder([package_name]):
            raise DistutilsModuleError(
                "some entries in registry '%s' defined for package '%s' "
                "failed to generate an artifact" % (
                    self.artifact_builder.registry_name,
                    package_name,
                )
            )
