#
# Copyright (C) 2018 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.  You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import os
import shutil

from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.constants import DEFAULT_VC_FONT
from pyanaconda.core.util import execWithCapture
from pyanaconda.core.path import join_paths
from pyanaconda.localization import get_locale_console_fonts, find_best_locale_match
from pyanaconda.modules.common.errors.installation import LanguageInstallationError, \
    KeyboardInstallationError
from pyanaconda.modules.common.task import Task
from pyanaconda.modules.localization.localed import get_missing_keyboard_configuration

log = get_module_logger(__name__)

X_CONF_DIR = "/etc/X11/xorg.conf.d"
X_CONF_FILE_NAME = "00-keyboard.conf"
VC_CONF_FILE_PATH = "/etc/vconsole.conf"


class LanguageInstallationTask(Task):
    """Installation task for the language configuration."""

    LOCALE_CONF_FILE_PATH = "/etc/locale.conf"
    LOCALE_FALLBACK = "C.UTF-8"

    def __init__(self, sysroot, lang):
        """Create a new task,

        :param sysroot: a path to the root of the installed system
        :param lang: a value for LANG locale variable
        """
        super().__init__()
        self._sysroot = sysroot
        self._lang = lang

    @property
    def name(self):
        return "Configure language"

    def run(self):
        """Run the installation task."""
        lang = self._get_supported_language()
        self._write_language_configuration(lang)

    def _get_supported_language(self):
        """Return a supported locale.

        :return: a locale
        """
        if self._is_language_support_installed(self._lang):
            return self._lang

        log.debug("The '%s' locale is unsupported.", self._lang)
        log.debug("Using the '%s' locale as a fallback.", self.LOCALE_FALLBACK)
        return self.LOCALE_FALLBACK

    def _is_language_support_installed(self, lang):
        """Is the support for the specified language installed?

        The language is considered to be supported if we are not
        able to determine the supported locales due to missing tools.

        :param lang: a value for the LANG locale variable
        :return: False if the locale is known to be not supported, otherwise True
        """
        try:
            output = execWithCapture("locale", ["-a"], root=self._sysroot)
        except OSError as e:
            log.warning("Couldn't get supported locales: %s", e)
            return True

        match = find_best_locale_match(lang, output.splitlines())

        log.debug("The '%s' locale matched '%s'.", lang, match)
        return bool(match)

    def _write_language_configuration(self, lang):
        """Write language configuration to the /etc/locale.conf file.

        :param lang: a value for the LANG locale variable
        """
        try:
            fpath = join_paths(self._sysroot, self.LOCALE_CONF_FILE_PATH)
            log.debug("Writing the '%s' locale to %s.", lang, fpath)

            with open(fpath, "w") as fobj:
                fobj.write('LANG="{}"\n'.format(lang))

        except OSError as e:
            msg = "Cannot write language configuration file: {}".format(e.strerror)
            raise LanguageInstallationError(msg) from e


class KeyboardInstallationTask(Task):
    """Installation task for the keyboard configuration."""

    def __init__(self, sysroot, localed_wrapper, x_layouts, switch_options, vc_keymap):
        """Create a new task,

        :param localed_wrapper: instance of systemd-localed service wrapper
        :type localed_wrapper: LocaledWrapper
        :param sysroot: a path to the root of the installed system
        :type sysroot: str
        :param x_layouts: list of x layout specifications
        :type x_layouts: list(str)
        :param switch_options: list of options for layout switching
        :type switch_options: list(str)
        :param vc_keymap: virtual console keyboard mapping name
        :type vc_keymap: str
        """
        super().__init__()
        self._sysroot = sysroot
        self._localed_wrapper = localed_wrapper
        self._x_layouts = x_layouts
        self._switch_options = switch_options
        self._vc_keymap = vc_keymap

    @property
    def name(self):
        return "Configure keyboard"

    def run(self):
        x_layouts = self._x_layouts
        vc_keymap = self._vc_keymap

        if not self._x_layouts or not self._vc_keymap:
            x_layouts, vc_keymap = get_missing_keyboard_configuration(
                self._localed_wrapper,
                self._x_layouts,
                self._vc_keymap
            )

        if x_layouts:
            write_x_configuration(
                self._localed_wrapper,
                x_layouts,
                self._switch_options,
                X_CONF_DIR,
                self._sysroot
            )
        if vc_keymap:
            write_vc_configuration(
                vc_keymap,
                self._sysroot
            )


def write_x_configuration(localed_wrapper, x_layouts, switch_options, x_conf_dir_path, root):
    """Write X keyboard layout configuration to the configuration files.

    :param localed_wrapper: instance of systemd-localed service wrapper
    :type localed_wrapper: LocaledWrapper
    :param x_layouts: list of X layout specifications
    :type x_layouts: list(str)
    :param switch_options: list of options for layout switching
    :type switch_options: list(str)
    :param x_conf_dir_path: path to directory holding X Layouts configuration
    :type x_conf_dir_path: str
    :param root: root path of the configured system
    :type root: str
    """

    errors = []
    try:
        if not os.path.isdir(x_conf_dir_path):
            os.makedirs(x_conf_dir_path)
    except OSError:
        # Non-fatal, systemd-localed may create the directory on its own.
        log.debug("Cannot create X Layouts configuration directory %s", x_conf_dir_path)

    if root != "/":
        # Writing to a different root, we need to save these values, so that
        # we can restore them when we have the file written out.
        saved_layouts_variants = localed_wrapper.layouts_variants
        saved_options = localed_wrapper.options

    # Set systemd-localed's layouts, variants and switch options, which
    # also generates a new conf file.
    localed_wrapper.set_layouts(x_layouts, switch_options)

    if root != "/":
        # Make sure the target directory exists under the given root.
        rooted_xconf_dir = os.path.normpath(root + "/" + x_conf_dir_path)
        try:
            if not os.path.isdir(rooted_xconf_dir):
                os.makedirs(rooted_xconf_dir)
        except OSError:
            errors.append("Cannot create directory {}".format(rooted_xconf_dir))

        # Copy the file to the chroot.
        xconf_file_path = os.path.normpath(x_conf_dir_path + "/" + X_CONF_FILE_NAME)
        rooted_xconf_file_path = os.path.normpath(root + "/" + xconf_file_path)
        try:
            shutil.copy2(xconf_file_path, rooted_xconf_file_path)
        except OSError as ioerr:
            log.error("Cannot copy X layouts configuration file %s to target system: %s.",
                      xconf_file_path, ioerr.strerror)

        # Restore the original values.
        localed_wrapper.set_layouts(saved_layouts_variants, saved_options)

    if errors:
        raise KeyboardInstallationError("\n".join(errors))


def write_vc_configuration(vc_keymap, root):
    """Write virtual console keyboard mapping configuration.

    :param vc_keymap: virtual console keyboard mapping name
    :type vc_keymap: str
    :param root: root path of the configured system
    :type root: str
    """
    try:
        fpath = os.path.normpath(root + VC_CONF_FILE_PATH)

        # "eurlatgr" may not be the best choice for all the languages,
        # for example, Russian, Urdu, Serbian, Sindhi and a few more.
        # refer: https://bugzilla.redhat.com/show_bug.cgi?id=1919486
        # assuming that LANG is set by now.
        vc_fonts = get_locale_console_fonts(os.environ["LANG"])
        vc_font = vc_fonts[0] if vc_fonts else DEFAULT_VC_FONT

        with open(fpath, "w") as fobj:
            fobj.write('KEYMAP="%s"\n' % vc_keymap)

            # systemd now defaults to a font that cannot display non-ascii
            # characters, so we have to tell it to use a better one
            fobj.write('FONT="%s"\n' % vc_font)

    except OSError as e:
        msg = "Cannot write vconsole configuration file: {}".format(e.strerror)
        raise KeyboardInstallationError(msg) from e
