# -*- coding: utf-8 -*-

# Copyright 2008-2016 Mir Calculate. http://www.calculate-linux.org
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import os
import sys
import re
from os import path
from calculate.lib.datavars import (Variable, ReadonlyVariable,
                                    ReadonlyTableVariable, FieldValue,
                                    HumanReadable, VariableInterface)
from calculate.lib.utils.files import (readFile,
                                       typeFile, process, listDirectory,
                                       MAGIC_SYMLINK, MAGIC_COMPRESS)

from calculate.lib.cl_lang import setLocalTranslate

_ = lambda x: x
setLocalTranslate('cl_install3', sys.modules[__name__])
from operator import itemgetter

from calculate.lib.utils.files import readLinesFile
from calculate.lib.utils.common import (getKernelUid, getTupleVersion,
                                        getValueFromCmdLine, CmdlineParams)
from itertools import *
from calculate.install.distr import DistributiveError


class VariableOsInstallKernelScheduler(Variable):
    """
    Install scheduler opts (cfq,bfq,none,deadline)
    """
    type = "choice"
    opt = ["--scheduler"]
    metavalue = "SCHEDULER"

    def init(self):
        self.help = _("toggle the I/O scheduler")
        self.label = _("I/O scheduler")

    def check_scheduler(self, scheduler):
        return scheduler in self.Select('os_install_kernel_schedule_name',
                                        where='os_install_kernel_schedule_set',
                                        eq='on')

    def get_default(self):
        root_dev = self.Select('os_install_disk_parent',
                               where='os_install_disk_mount',
                               eq='/', limit=1)
        if root_dev:
            dev_ssd, dev_virtual = self.Select(['os_device_ssd_set',
                                                'os_device_virtual_set'],
                                               where='os_device_dev',
                                               eq=root_dev, limit=1)
            if ((dev_ssd == 'on' or dev_virtual == 'on') and
                    self.check_scheduler("noop")):
                return "noop"
        return self.Get('os_install_kernel_schedule_default')

    def get(self):
        """Get scheduler for install root device"""
        if self.Get('os_root_type') == 'livecd':
            return self.get_default()
        else:
            currentScheduler = getValueFromCmdLine(
                CmdlineParams.IOScheduler)
            if currentScheduler in map(lambda x: x[0], self.choice()):
                return currentScheduler
        return self.Get('os_install_kernel_schedule_default')

    def choice(self):
        schedulers = {"deadline": "Deadline",
                      "cfq": "CFQ",
                      "noop": "No-op",
                      "bfq": "BFQ"}
        return [(x, schedulers.get(x, x))
                for x in self.Select('os_install_kernel_schedule_name',
                                     where='os_install_kernel_schedule_set',
                                     eq='on')] + [("auto", _("Default"))]

    def set(self, value):
        if value == "auto":
            return self.get_default()
        return value

    def uncompatible(self):
        """
        Unavailable for flash installation
        """
        if self.Get('os_install_root_type') == 'flash':
            return _("I/O scheduler unavailable for Flash install")


class KernelConfig:
    def __init__(self, kernel_config):
        self.data = readFile(kernel_config).split('\n')
        self.config = kernel_config

    def __iter__(self):
        return iter(self.data)

    def __str__(self):
        return "kernel config (%s)" % self.config

    def __contains__(self, item):
        if "=" in item:
            if item.endswith("=n"):
                key = "# %s is not set" % item[:-2]
            else:
                key = item
        else:
            key = "%s=" % item
        return any(key in x for x in self)


class VariableOsInstallKernelConfig(ReadonlyVariable):
    """
    Install config kernel filename
    """

    def get_kernel_src(self, distr_path):
        """
        Get version of kernel from .config
        """
        kernel_src = 'usr/src/linux'
        makefile_path = path.join(distr_path, kernel_src, "Makefile")

        # get version from Makefile
        re_makefile = re.compile("^VERSION = (\S+)\n"
                                 "PATCHLEVEL = (\S+)\n"
                                 "SUBLEVEL = (\S+)\n"
                                 "EXTRAVERSION = (\S*)\n", re.M)
        if path.exists(makefile_path):
            with open(makefile_path) as f:
                match = re_makefile.search(f.read(200))
                if match:
                    return "{0}.{1}.{2}{3}".format(*match.groups())
        return ""

    def configs(self, distr_path):
        src_kernel_ver = self.get_kernel_src(distr_path)
        if src_kernel_ver:
            yield path.join("boot", "config-%s" % src_kernel_ver)
        if self.Get('cl_chroot_path') == '/':
            ver = process('/bin/uname', '-r').read().strip()
            yield path.join("boot", "config-%s" % ver)
        yield 'usr/src/linux/.config'

    def get(self):
        image = self.Get('cl_image')
        if image:
            with image:
                try:
                    distrPath = image.getDirectory()
                    for config in self.configs(distrPath):
                        config_name = path.join(distrPath, config)
                        if path.exists(config_name):
                            return KernelConfig(config_name)
                except DistributiveError:
                    return ""
        return ""

class VariableOsKernelConfig(VariableOsInstallKernelConfig):
    """
    Current config kernel
    """
    def configs(self, distr_path):
        ver = process('/bin/uname', '-r').read().strip()
        yield path.join("boot", "config-%s" % ver)
        yield 'usr/src/linux/.config'

    def get(self):
        for config in self.configs("/"):
            config_name = path.join("/", config)
            if path.exists(config_name):
                return KernelConfig(config_name)
        return ""


class VariableOsInstallKernelScheduleDefault(Variable):
    """
    IO планировщик по умолчанию
    """
    type = "choice"

    def get(self):
        for line in self.Get('os_install_kernel_config'):
            if "CONFIG_DEFAULT_IOSCHED=" in line:
                key, op, value = line.partition("=")
                return value.strip('"')
        return "cfq"

    def choice(self):
        return self.Select('os_install_kernel_schedule_name',
                           where='os_install_kernel_schedule_set',
                           eq='on')


class VariableOsInstallKernelScheduleData(ReadonlyTableVariable):
    """
    Information about kernel schedule
    """
    source = ['os_install_kernel_schedule_name',
              'os_install_kernel_schedule_set']

    def get(self, hr=HumanReadable.No):
        schedulers = {'CONFIG_IOSCHED_BFQ=y': 'bfq',
                      'CONFIG_IOSCHED_NOOP=y': 'noop',
                      'CONFIG_IOSCHED_CFQ=y': 'cfq',
                      'CONFIG_IOSCHED_DEADLINE=y': 'deadline'}
        installed = map(schedulers.get,
                        filter(lambda x: x in schedulers,
                               self.Get('os_install_kernel_config'))) or ['cfq']
        return [[x, "on" if x in installed else "off"]
                for x in sorted(schedulers.values())]

    setValue = Variable.setValue


class VariableOsInstallKernelScheduleName(FieldValue, ReadonlyVariable):
    """
    Schedule name
    """
    type = "list"
    source_variable = "os_install_kernel_schedule_data"
    column = 0


class VariableOsInstallKernelScheduleSet(FieldValue, ReadonlyVariable):
    """
    Kernel has schedule
    """
    type = "list-bool"
    source_variable = "os_install_kernel_schedule_data"
    column = 1


class VariableOsInstallKernelTuxoniceSet(ReadonlyVariable):
    """
    Available BFQ in kernel
    """
    type = "bool"

    def get(self):
        if any("CONFIG_TOI_CORE=y" in x
               for x in self.Get('os_install_kernel_config')):
            return "on"
        return "off"


class VariableOsInstallKernelBfqSet(ReadonlyVariable):
    """
    Available BFQ in kernel
    """
    type = "bool"

    def get(self):
        if any("CONFIG_IOSCHED_BFQ=y" in x
               for x in self.Get('os_install_kernel_config')):
            return "on"
        return "off"


class VariableOsInstallKernelAttr(Variable):
    """
    Install kernel attributes
    """

    def get(self):
        # on usb-hdd install must be "delay=5"
        attr = ""
        rdauto = ""
        if self.Get('os_install_root_type') == 'usb-hdd':
            attr = " scandelay=5"
        if self.Get('os_install_mdadm_set') == 'on':
            attr += " domdadm"
            rdauto = " rd.auto"
        if self.Get('os_install_lvm_set') == 'on':
            attr += " dolvm"
            if not rdauto:
                rdauto = " rd.auto"
        return attr + rdauto


class VariableOsInstallKernelResume(ReadonlyVariable):
    """
    Install kernel resume
    """

    def get(self):
        """install kernel resume parameter"""
        for dev, install in zip(self.Get('os_install_disk_use'),
                                self.Get('os_install_disk_mount')):
            if install == "swap":
                if self.Get('os_install_kernel_tuxonice_set') == 'on':
                    return "tuxonice tuxonice_resume=%s real_resume=%s" % (
                        dev, dev)
                else:
                    return "real_resume=%s" % dev
        return ""


class KernelHelper(VariableInterface):
    """
    Helper for kernel variables
    """
    reFindVer = re.compile(
        "(?<=version )(\d+\.?\d*\.?\d*\.?\d*)([^\d* ])*(\d*)")

    def getFilesByType(self, pathname, descr):
        """Get files from "pathname" has "descr" in descriptions"""
        filelist = map(lambda x: path.join(pathname, x), os.listdir(pathname))
        ftype = typeFile(magic=MAGIC_COMPRESS | MAGIC_SYMLINK).getMType
        filesWithType = map(lambda x: (x, ftype(x)),
                            filter(path.exists,
                                   filelist))
        return filter(lambda x: descr in x[1], filesWithType)

    def getInitrd(self, arch, shortname, chroot, kernel, suffix="",
                  notsuffix=""):
        """Get initrd for kernel"""
        reInitrdVer = re.compile("(initrd|initramfs)-(.+?)(-install)?$", re.S)

        def initrd_version_by_name(filename):
            resInitrdVer = reInitrdVer.search(filename)
            if resInitrdVer:
                return resInitrdVer.groups()[1]
            return ""

        ftype = typeFile(magic=MAGIC_COMPRESS | MAGIC_SYMLINK).getMType
        kernelfile = path.join(chroot, 'boot', kernel)
        typeKernelFile = ftype(kernelfile)
        if typeKernelFile is None:
            return ""
        resKernelVer = self.reFindVer.search(ftype(kernelfile))
        if resKernelVer:
            kernelVersion = "%s-%s-%s" % \
                            (resKernelVer.group().replace('-calculate', ''),
                             arch, shortname)
            origKernelVer = resKernelVer.group()

            bootdir = path.join(chroot, 'boot')
            initramfsFiles = self.getFilesByType(bootdir, "ASCII cpio archive")
            initramfsWithVer = \
                filter(lambda x: (kernelVersion in x[1] or
                                  origKernelVer in x[1]) and \
                                 x[0].endswith(suffix) and \
                                 (
                                 not notsuffix or not x[0].endswith(notsuffix)),
                       map(lambda x: (x[0], initrd_version_by_name(x[0])),
                           initramfsFiles))
            if initramfsWithVer:
                return path.split(min(initramfsWithVer,
                                      key=itemgetter(0))[0])[-1]
        return ""


class VariableOsInstallKernel(ReadonlyVariable, KernelHelper):
    """
    Kernel filename
    """

    def get(self):
        bootdir = path.join(self.Get('cl_chroot_path'), 'boot')
        modulesdir = path.join(self.Get('cl_chroot_path'), 'lib/modules')
        validKernel = listDirectory(modulesdir)
        kernelFiles = self.getFilesByType(bootdir, "Linux kernel")
        installMarch = self.Get('os_install_arch_machine')
        kernelsWithVer = \
            map(lambda x: (
            x[0], (getTupleVersion("".join(x[1].groups()[0:3:2])),
                   path.getmtime(x[0]))),
                # convert version to tuple( versionTuple, mtime)
                # version detect, for this version lib contains moudules
                # kernel arch equal install arch
                ifilter(lambda x: x[1] and x[1].group() in validKernel and
                                  installMarch in x[0].rpartition('/')[2],
                        # (filename,version)
                        imap(lambda x: (x[0], self.reFindVer.search(x[1])),
                             kernelFiles)))
        if kernelsWithVer:
            return path.split(max(kernelsWithVer, key=itemgetter(1))[0])[-1]
        else:
            return "vmlinuz"


class VariableOsInstallInitrd(ReadonlyVariable, KernelHelper):
    """
    Optimized initramfs filename
    """

    def get(self):
        return self.getInitrd(self.Get('os_install_arch_machine'),
                              self.Get('os_install_linux_shortname'),
                              self.Get('cl_chroot_path'),
                              self.Get('os_install_kernel'),
                              suffix="", notsuffix="-install") or \
               self.getInitrd(self.Get('os_install_arch_machine'),
                              self.Get('os_install_linux_shortname'),
                              self.Get('cl_chroot_path'),
                              self.Get('os_install_kernel'),
                              suffix="-install")[:-8] \
               or "initrd"


class VariableOsInstallInitrdInstall(ReadonlyVariable, KernelHelper):
    """
    Install initramfs filename
    """

    def get(self):
        return self.getInitrd(self.Get('os_install_arch_machine'),
                              self.Get('os_install_linux_shortname'),
                              self.Get('cl_chroot_path'),
                              self.Get('os_install_kernel'),
                              suffix="-install") or "initrd-install"


class VariableOsInstallSystemMap(ReadonlyVariable):
    """
    Install system map filename
    """

    def get(self):
        systemmapfile = self.Get('os_install_kernel').replace('vmlinuz',
                                                              'System.map')
        if systemmapfile.startswith('System.map') and path.exists(
                path.join(self.Get('cl_chroot_path'), 'boot', systemmapfile)):
            return systemmapfile
        else:
            return ""


class VariableOsInstallKernelCpufreq(ReadonlyVariable):
    """
    Cpufreq modules
    """

    def get(self):
        """Get cpufreq (and other from modules_3= param) from conf.d/modules"""
        cpufreqmods = map(lambda x: x.partition('=')[2].strip("\n '\""),
                          filter(lambda x: x.startswith('modules_3'),
                                 readLinesFile('/etc/conf.d/modules')))
        if cpufreqmods:
            return cpufreqmods[0]
        else:
            return ""


class VariableClInstallKernelUid(ReadonlyVariable):
    """
    Variable install kernel UID
    """

    def get(self):
        return getKernelUid(self.Get('os_install_root_dev'))
