# -*- 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
import hashlib
import glob
from calculate.lib.datavars import Variable, VariableError, ReadonlyVariable
from calculate.lib.utils.portage import isPkgInstalled
from calculate.lib.utils.files import readFile, readFileEx
from calculate.lib.utils.tools import get_best_nearest_resolution
import calculate.lib.utils.device as device
from calculate.lib.utils.common import (getVideoFromXorgLog,
                                        getVideoFromXorgConf,
                                        getVideoFromCmdLine,
                                        getAvailableVideo, getValueFromCmdLine,
                                        getCompositeFromXorgconf,
                                        getVideoFromModules,
                                        getVideoFromVendor, getInstalledVideo,
                                        CmdlineParams)
from calculate.lib.utils.video import get_edid_data, EdidInfoError, EdidInfo
from calculate.install.distr import DistributiveError
import fcntl
import struct
from collections import OrderedDict

from calculate.lib.cl_lang import setLocalTranslate, _

setLocalTranslate('cl_install3', sys.modules[__name__])


class VideoVariable(Variable):
    """
    Video variables not using for flash installation
    """
    xorg_need = True
    default_video = "default"
    driver_names = OrderedDict([
        ('default', _("Auto detection")),
        ('radeon', _("AMD Radeon (radeon)")),
        ('amdgpu', _("AMD AMDGPU (amdgpu)")),
        ('fglrx', _("AMD Catalyst (fglrx)")),
        ('modesetting', _("Framebuffer device (modesetting)")),
        ('vesa', _("Generic VESA (vesa)")),
        ('intel', _("Intel (intel)")),
        ('nouveau', _("Nvidia Nouveau (nouveau)")),
        ('nvidia', _("Nvidia Graphics Driver (nvidia)")),
    ])

    def uncompatible(self):
        """
        Video setting up unavailable for flash installation
        """
        if self.Get('os_install_root_type') == 'flash':
            return \
                _("Video configuration unavailable for Flash install")
        if (self.Get('install.os_install_x11_server_set') == 'off' and
                self.xorg_need):
            return \
                _("This distribution does not provide a Xorg server")
        return ""


class ResolutionVariable(VideoVariable):
    """
    Abstract resolution variable
    """
    fbres = False

    def choice(self):
        resolutions = ["640x480", "800x480", "800x600", "1024x576", "1024x600",
                       "1024x768", "1200x800", "1280x800", "1280x720",
                       "1280x768", "1280x1024", "1360x768", "1366x768",
                       "1368x768", "1400x1050", "1440x900", "1680x945",
                       "1680x1050", "1920x1080", "1920x1200", "1600x768",
                       "1600x900", "1600x1200", "2048x1152", "2560x1440",
                       "2560x1600"]
        if self.fbres:
            return map(lambda x: "%s-32" % x,
                       resolutions)
        else:
            return resolutions

    def check(self, value):
        """
        Check resolution format 1234x567
        """
        if not re.match('^\d+x\d+(-\d+(@\d+)?)?$', value):
            raise VariableError(
                _("Wrong resolution {resolution} {example}").format(
                    resolution=value,
                    example="(%s:%s)" % (_("Example"), "1024x768")))


class VariableOsInstallX11ResolutionPreferred(ResolutionVariable):
    """
    X.org resolution
    """
    type = 'choiceedit'
    opt = ['-X']
    metavalue = "<width>x<height>"
    # разрешение по умолчанию пустое - это нужно для livecd
    # для автоопределения разрешения xorg сервером
    preferred_resolution = ""

    def init(self):
        self.help = _("set the Xorg resolution")
        self.label = _("Screen resolution")

    def get(self):
        # get resolution from xorg.log
        res = self.Get('os_x11_resolution')
        if res:
            return res
        else:
            return self.preferred_resolution

class VariableOsInstallX11Resolution(ResolutionVariable):
    """
    X.org resolution
    """
    fallback_resolution = "1024x768"
    FBIOGET_VSCREENINFO = 0x4600

    def framebuffer_resolution(self):
        try:
            fbdev = os.open('/dev/fb0', os.O_RDONLY)
            data = fcntl.ioctl(fbdev, self.FBIOGET_VSCREENINFO, " " * 8)
            res = struct.unpack("II", data)
            return "%sx%s" % (res[0], res[1])
        except (IOError, OSError):
            pass
        return ""

    def get(self):
        # get resolution from xorg.log
        res = self.Get('install.os_install_x11_resolution_preferred')
        if res:
            return res
        res = self.framebuffer_resolution()
        if res:
            return res
        return self.fallback_resolution

class VariableOsInstallX11VideoAvailable(VideoVariable):
    """
    Get available (already installed or installable drivers
    """
    type = "list"
    # supported = ["nvidia", "fglrx", "amdgpu", "nouveau", "intel", "radeon"]
    supported = ["nvidia", "fglrx", "amdgpu",
                 "modesetting",
                 "nouveau", "intel", "radeon", "vesa"]

    def get(self):
        image = self.Get('cl_image')
        if image:
            with image:
                try:
                    distrPath = image.getDirectory()
                    if isPkgInstalled('xorg-server', prefix=distrPath):
                        return (sorted(filter(self.supported.__contains__,
                                              getAvailableVideo(
                                                  prefix=distrPath))) +
                                [self.default_video])
                except DistributiveError:
                    pass
        return []

    def humanReadable(self):
        return map(lambda x: self.driver_names.get(x, x), self.Get())


class VariableOsX11KmsVideoDrv(ReadonlyVariable):
    """
    Список KMS драйверов
    """
    type = "list"
    value = ["radeon", "intel", "nouveau", "amdgpu", "modesetting"]


class VariableOsInstallX11VideoDrv(VideoVariable):
    """
    Video driver used by xorg
    """
    type = 'choiceedit'
    opt = ['--video']
    metavalue = "VIDEODRV"

    def init(self):
        self.help = _("set the video driver")
        self.label = _("Video driver")

    def nox_video_drivers(self):
        values = self.Get('os_x11_kms_video_drv')
        for drv, drvinfo in self.pkgDrvMap.items():
            _, pkgdrv = drvinfo
            if isPkgInstalled(pkgdrv, prefix=self.Get('cl_chroot_path')):
                values.append(drv)
        return [self.default_video] + list(sorted(values))

    def choice(self):
        """Get available (already installed or installable drivers"""
        if self.Get('os_install_x11_server_set') == 'on':
            values = self.Get('os_install_x11_video_available')
        else:
            values = self.nox_video_drivers()
        return map(lambda x: (x, self.driver_names.get(x, x)),
                   (x for x in self.driver_names.keys() if x in values))

    def get(self):
        if self.Get('os_install_x11_server_set') == 'on':
            # get available videodriver list from install or configure distributive
            list_video = self.Choice('os_install_x11_video_drv')
            if not list_video:
                return self.default_video
            # if type system is usb-hdd then get detect video driver
            if self.Get('os_install_root_type') == 'usb-hdd':
                methods = ((getVideoFromModules, ()),
                           (getVideoFromCmdLine, ()),
                           (getVideoFromVendor,
                            (self.Get('hr_video'), list_video)))
            else:
                # test current video driver for install system
                methods = ((getVideoFromXorgLog, ('/', list_video)),
                           (getVideoFromXorgConf, ('/',)),
                           (getVideoFromCmdLine, ()),
                           (getVideoFromModules, ()),
                           (getVideoFromVendor,
                            (self.Get('hr_video'), list_video)))
            for func, args in methods:
                drv = func(*args)
                if drv in list_video:
                    return drv
            return self.default_video
        else:
            for drv in map(lambda x: x[0], self.choice()):
                refcnt = device.sysfs.read(
                    device.sysfs.Path.Module, drv, "refcnt").strip()
                if refcnt.isdigit() and int(refcnt) > 0:
                    return {'i915': 'intel'}.get(drv, drv)
            else:
                return self.default_video

    pkgDrvMap = {'nvidia': ('NVidia', 'x11-drivers/nvidia-drivers'),
                 'fglrx': ('ATI', 'x11-drivers/ati-drivers'),
                 'vboxdrv': ('VirtualBox', 'x11-drivers/xf86-video-virtualbox')}

    def check(self, value):
        if self.Get('os_install_x11_server_set') == 'on':
            if self.Get('cl_action') == 'system':
                availDrvs = self.Get('os_install_x11_video_available')
                if not value in availDrvs:
                    raise VariableError(_("Only %s drivers are available") %
                                        ",".join(availDrvs))
            else:
                if not value in getInstalledVideo(prefix="/") and \
                        not value in ("auto", self.default_video):
                    error = _("video driver %s is unavailable") % value
                    if value in self.pkgDrvMap:
                        error += ". " + (_("Install driver %s with:")
                                         % self.pkgDrvMap[value][0])
                        error += "\n" + ("emerge %s" % self.pkgDrvMap[value][1])
                    raise VariableError(error)
        else:
            availDrivers = self.nox_video_drivers()
            if not value in availDrivers:
                raise VariableError("Only %s drivers are available" %
                                    ",".join(availDrivers))

    def uncompatible(self):
        """
        Video setting up unavailable for flash installation
        """
        if self.Get('os_install_root_type') == 'flash':
            return \
                _("Video configuration unavailable for Flash install")
        return ""


class VariableOsInstallX11VideoDrvPrev(VariableOsInstallX11VideoDrv):
    """
    Предыдущее значение os_install_x11_videodrv
    """


class VariableHrVideoId(ReadonlyVariable):
    """
    BusID of video card
    TODO: need realization
    """
    value = ""


class VariableOsInstallX11Composite(VideoVariable):
    """
    on/off composite
    """
    type = 'bool'
    opt = ['--composite']

    def init(self):
        self.help = _("toggle composite")
        self.label = _("Composite")

    def get(self):
        """On/off composite"""
        defaultCompositeOn = ("nvidia", "intel", "fglrx",  "amdgpu",
                              "modesetting",
                              "nouveau", "radeon", "default")
        composite = getValueFromCmdLine(CmdlineParams.Calculate,
                                        CmdlineParams.Composite)
        videodrv = getValueFromCmdLine(CmdlineParams.Calculate,
                                       CmdlineParams.Video)
        if videodrv != "auto":
            composite = {'nocomposite': 'off',
                         'off': 'off',
                         'on': 'on',
                         'composite': 'on'}.get(composite)
        else:
            composite = None

        if self.Get('os_install_x11_video_drv') in defaultCompositeOn:
            defaultComposite = "on"
        elif self.Get('hr_virtual') == 'vmware':
            defaultComposite = "on"
        else:
            defaultComposite = "off"
        if self.Get('os_install_x11_video_drv') == self.Get('os_x11_video_drv'):
            state = getCompositeFromXorgconf()
        else:
            state = None
        return composite or state or defaultComposite

class VariableOsInstallFbResolutionPreferred(ResolutionVariable):
    """
    Framebuffer resolution
    """
    type = 'choiceedit'
    opt = ['--fb']
    metavalue = "<width>x<height>"
    xorg_need = False
    fbres = True
    value = "auto"

    def init(self):
        self.help = _("set the framebuffer resolution")
        self.label = _("Framebuffer resolution")

    def choice(self):
        yield ("auto", _("Auto"))
        for i in ResolutionVariable.choice(self):
            yield (i,i)

    def check(self, value):
        if value == "auto":
            return
        ResolutionVariable.check(self, value)

class VariableOsInstallFbResolution(ResolutionVariable):
    """
    Framebuffer resolution
    """
    type = 'choiceedit'
    opt = ['--fb']
    metavalue = "<width>x<height>"
    xorg_need = False
    fbres = True
    fallback_resolution = "1024x768"

    def init(self):
        self.help = _("set the framebuffer resolution")
        self.label = _("Framebuffer resolution")

    def using_kms(self):
        drv = self.Get('install.os_install_x11_video_drv')
        kms = self.Get('install.os_x11_kms_video_drv')
        return drv in kms

    def using_uefi(self):
        return self.GetBool('install.os_install_uefi_set')

    def get(self):
        custom = self.Get('os_install_fb_resolution_preferred')
        if custom != "auto":
            return custom
        x11res = self.Get('os_install_x11_resolution')
        if self.using_kms() or self.using_uefi():
            return x11res
        hwinfo = device.Hwinfo()
        try:
            return get_best_nearest_resolution(
                x11res, hwinfo.resolutions()) or self.fallback_resolution
        except device.HwinfoError:
            return self.fallback_resolution

class VariableClGrubImageHash(ReadonlyVariable):
    """
    Контрольная сумма изображения для grub
    """
    grub_image = "/boot/grub/grub-calculate.png"
    theme_data = "/etc/grub.d/05_theme"

    def get_image_md5(self, source):
        return hashlib.md5(readFile(source)).hexdigest()

    def get_config_md5(selfself, source):
        return hashlib.md5(readFileEx(source, grab=True)).hexdigest()

    def get_checksum(self):
        data = []
        if path.exists(self.grub_image):
            data.append(self.get_image_md5(self.grub_image))
        else:
            data.append("-")
        if path.exists(self.theme_data):
            data.append(self.get_config_md5(self.theme_data))
        else:
            data.append("-")
        return "".join(data)

    def get(self):
        if self.Get('cl_setup') == 'themes':
            return self.get_checksum()
        return ""

class VariableClGrubImageUpdateSet(VariableClGrubImageHash):
    """
    Изображение для grub обновлилось
    """

    def get(self):
        if self.Get('cl_setup') == 'themes':
            newmd5 = self.get_checksum()
            return "on" if newmd5 != self.Get('cl_grub_image_hash') else "off"
        return "off"

class VariableClSplashImageHash(ReadonlyVariable):
    """
    Контрольные суммы изображений для splashutils
    """
    hash_files = ("/etc/splash/calculate/images/verbose.md5",
                  "/etc/splash/calculate/images/silent.md5",
                  "/usr/share/plymouth/themes/calculate/boot.md5",
                  "/usr/share/plymouth/themes/calculate/boot/md5sum",
                  "/usr/share/plymouth/themes/calculate/calculate.plymouth")

    cfg_files = "/etc/splash/calculate/*.cfg"

    def get_config_md5(selfself, source):
        return hashlib.md5(readFileEx(source, grab=True)).hexdigest()

    def get_hash_data(self, sources):
        data = []
        for fn in sources:
            data.append(self.get_config_md5(fn))
        for fn in glob.glob(self.cfg_files):
            data.append(self.get_config_md5(fn))
            break
        return "".join(data)

    def get(self):
        if self.Get('cl_setup') == 'themes':
            return self.get_hash_data(self.hash_files)
        return ""

class VariableClSplashImageUpdateSet(VariableClSplashImageHash):
    """
    Изображение для splash dracut обновлилось
    """
    def get(self):
        if self.Get('cl_setup') == 'themes':
            newmd5 = self.get_hash_data(self.hash_files)
            return "on" if newmd5 != self.Get('cl_splash_image_hash') else "off"
        return "off"

class VariableClInstallEdidData(ReadonlyVariable):
    type = Variable.Types.Object

    def get(self):
        edid_data = get_edid_data()
        if not edid_data:
            return {}
        try:
            ei = EdidInfo()
            ei.set_data(edid_data)
            return {
                "resolution": ei.resolution,
                "ratio": ei.ratio,
                "screensize": ei.screensize
            }
        except EdidInfoError as e:
            return {}

class VariableClInstallEdidResolution(ReadonlyVariable):
    def get(self):
        return self.Get('cl_install_edid_data').get('resolution','')

class VariableClInstallEdidScreensize(ReadonlyVariable):
    def get(self):
        return self.Get('cl_install_edid_data').get('screensize','')

class VariableClInstallCalculateDpi(Variable):
    def get(self):
        inch = 25.4
        screensize = self.Get('cl_install_edid_screensize')
        resolution = self.Get('os_install_x11_resolution')
        if screensize and resolution:
            cx = screensize.partition("x")[0]
            cxres = resolution.partition("x")[0]
            if cx.isdigit() and cxres.isdigit():
                cx = float(cx)
                cxres = float(cxres)
                return str(int(inch * cxres / cx))
        return ""

class VariableClInstallDpi(Variable):
    def get(self):
        calculate_dpi = self.Get('cl_install_calculate_dpi')
        try:
            if calculate_dpi:
                calculate_dpi = int(calculate_dpi)
                if calculate_dpi > 100:
                    return "108"
        except ValueError:
            pass
        return "96"

class VariableClInstallScaling(Variable):
    def get(self):
        dpi = self.Get('cl_install_dpi')
        try:
            if dpi:
                dpi = int(dpi)
                if dpi > 100:
                    return "hi"
        except ValueError:
            pass
        return "normal"
