# -*- 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
from os import path, R_OK
import platform
import re
from calculate.lib.utils.dracut import Dracut
from calculate.lib.datavars import Variable, ReadonlyVariable
from calculate.lib.utils.files import (readFile, process, listDirectory,
                                       pathJoin)
from calculate.lib.utils.mount import isMount, Mounts
from calculate.lib.utils.system import SystemType
import calculate.lib.utils.device as device
from calculate.lib.utils.device import loadEfiVars
from calculate.lib.utils.common import getKernelUid, getValueFromCmdLine, \
    CmdlineParams



class VariableOsArchMachine(ReadonlyVariable):
    """
    Computer architecture (i686,x86_64)
    """

    def get(self):
        return platform.machine()


class VariableOsArchMachineGentoo(ReadonlyVariable):
    """
    Архитектура пакетов
    """
    source_arch = "os_arch_machine"

    def get(self):
        map_arch = {'x86_64': 'amd64', 'i686': 'x86', 'i386': 'x86'}
        arch = self.Get(self.source_arch)
        return map_arch.get(arch, arch)


class VariableOsRootDev(ReadonlyVariable):
    """
    Root partition of current system
    """

    def get(self):
        """Root filesystem device"""
        record = readFile('/proc/cmdline').strip()
        re_resRealRoot = re.search('(?:^|\s)real_root=(\S+)(\s|$)', record)
        re_resFakeRoot = re.search('(?:^|\s)root=(\S+)(\s|$)', record)
        # param real_root priority that root
        re_res = re_resRealRoot or re_resFakeRoot
        if re_res:
            rootparam = re_res.group(1)
            # check root for /dev/sd view
            if re.match("^/dev/[a-z]+.*$", rootparam):
                return device.udev.get_device_info(
                    name=rootparam.strip()).get('DEVNAME', rootparam)
            # check root set by uuid
            if re.match("^UUID=.*$", rootparam):
                uuid = rootparam[5:].strip("\"'")
                blkid_process = process('/sbin/blkid', '-c', '/dev/null', '-U',
                                        uuid)
                if blkid_process.success():
                    return device.udev.get_device_info(
                        name=blkid_process.read().strip()).get('DEVNAME', '')
            # check root set by label
            if re.match("^LABEL=.*$", rootparam):
                uuid = rootparam[6:].strip("\"'")
                blkid_process = process('/sbin/blkid', '-c', '/dev/null', '-L',
                                        uuid)
                if blkid_process.success():
                    return device.udev.get_device_info(
                        name=blkid_process.read().strip()).get('DEVNAME', '')
        # get device mounted to root
        df_process = process('df', '/', envdict={'LANG': 'C'})
        if df_process.failed():
            return ""
        df_lines = df_process.readlines()
        if len(df_lines) > 1:
            root_dev = df_lines[1].split(" ")[0].strip()
            if root_dev:
                return {
                    'none': '/dev/ram0',
                    'overlay': '/dev/ram0',
                }.get(root_dev, root_dev)
        return ""


class VariableOsMapperRootDev(ReadonlyVariable):
    """
    Название диска через devicemapper если возможно
    """

    def get(self):
        rootdev = self.Get('os_root_dev')
        dev_links = device.udev.get_device_info(
            name=rootdev).get("DEVLINKS", "")
        if dev_links:
            mapper_name = filter(lambda x: "/dev/mapper" in x,
                                 dev_links.split())
            if mapper_name:
                return mapper_name[0]
        return rootdev


class RootType(object):
    """
    Типы загруженной системы
    """

    class Value(object):
        HDD = "hdd"
        LiveCD = "livecd"
        USB_HDD = "usb-hdd"
        LiveFlash = "flash"
        IsoScanGrub = "isoscan-grub"
        IsoScanFlash = "isoscan-flash"
        NetBoot = "nfs"

    HDD = (Value.HDD,)
    LiveCD = (Value.LiveCD,)
    USB_HDD = (Value.USB_HDD,)
    HDDs = (Value.HDD, Value.USB_HDD,)
    LiveFlash = (Value.LiveFlash,)
    IsoScanGrub = (Value.IsoScanGrub,)
    IsoScanFlash = (Value.IsoScanFlash,)
    NetBoot = (Value.NetBoot,)
    Live = (Value.LiveCD, Value.LiveFlash, Value.IsoScanFlash,
            Value.IsoScanGrub, Value.NetBoot)
    IsoScan = (Value.IsoScanFlash, Value.IsoScanGrub)

    RebuildAvailable = (Value.LiveFlash, Value.IsoScanFlash)
    Grubable = (Value.HDD, Value.USB_HDD, Value.IsoScanGrub)


class VariableOsRootType(ReadonlyVariable):
    """
    Root type (ram, hdd, usb-hdd, livecd)
    """

    def get(self):
        """Root device type (ram, hdd, livecd)"""
        extended_type = self.Get('os_root_type_ext')
        if extended_type in RootType.Live:
            return "livecd"
        if extended_type in RootType.USB_HDD:
            return "usb-hdd"
        return "hdd"


class VariableClIsoscanPath(ReadonlyVariable):
    """
    Образ с которого загружена система
    """

    def get(self):
        return getValueFromCmdLine(CmdlineParams.IsoscanFile) or ""


class VariableClIsoscanBasePath(ReadonlyVariable):
    """
    Путь куда монтируется устройство isoscan
    """
    value = Dracut.IsoScanPath


class VariableClIsoscanFullpath(ReadonlyVariable):
    """
    Путь в системе до образа с которого загружена система
    """

    def get(self):
        isoscan_path = self.Get('cl_isoscan_path')
        if isoscan_path:
            isoscan_base_path = self.Get('cl_isoscan_base_path')
            return pathJoin(isoscan_base_path, isoscan_path)
        return ""


class VariableOsRootFlashDev(ReadonlyVariable):
    """
    Флешка с которой загружена система
    """

    def get(self):
        root_type = self.Get('os_root_type_ext')
        if root_type in RootType.LiveFlash:
            mounts = Mounts()
            return mounts.getBy(mounts.NAME, where=mounts.DIR,
                                eq=Dracut.IsoLive)
        elif root_type in RootType.IsoScanFlash:
            mounts = Mounts()
            return mounts.getBy(mounts.NAME, where=mounts.DIR,
                                eq=Dracut.IsoScanPath)
        return ""


class VariableOsRootTypeExt(ReadonlyVariable):
    """
    Расширенный тип загруженной системы
    """

    def get(self):
        def link2pair(linkfile):
            """Return pair (target,link) from link"""
            basedir = path.dirname(linkfile)
            targetfile = os.readlink(linkfile)
            return path.normpath(os.path.join(basedir, targetfile)), linkfile

        root_dev = self.Get("os_root_dev")
        if root_dev:
            if "/dev/nfs" in root_dev:
                return RootType.Value.NetBoot

            if "/dev/ram" in root_dev:
                record = readFile('/proc/cmdline').strip()
                if self.Get('cl_isoscan_path'):
                    grub_cfg = path.join(
                        path.dirname(self.Get('cl_isoscan_fullpath')),
                        "grub.cfg")
                    if path.exists(grub_cfg):
                        return RootType.Value.IsoScanGrub
                    else:
                        return RootType.Value.IsoScanFlash
                elif "root=live:UUID" in record:
                    return RootType.Value.LiveFlash
                return RootType.Value.LiveCD

            id_dict = dict(map(link2pair,
                               filter(lambda x: path.islink(x),
                                      map(lambda x: path.join('/dev/disk/by-id',
                                                              x),
                                          listDirectory('/dev/disk/by-id')))))
            if "usb-" in id_dict.get(root_dev, ""):
                return RootType.Value.USB_HDD
        return RootType.Value.HDD


class VariableClChrootStatus(ReadonlyVariable):
    """
    Is chroot active
    """

    def isChroot(self, pid):
        """Detect chroot mode by different mountinfo"""
        if not os.access('/proc/self/mountinfo', R_OK) or \
                not os.access('/proc/1/mountinfo', R_OK):
            return False
        infos = [open(x, 'r').read() for x in ('/proc/1/mountinfo',
                                               '/proc/self/mountinfo')]
        return infos[0] != infos[1]

    def get(self):
        try:
            return "on" if self.isChroot(os.getpid()) else "off"
        except Exception:
            return "off"


class VariableClKernelUid(Variable):
    """
    Kernel UID get by UUID of root device
    """

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


class VariableOsScratch(ReadonlyVariable):
    """
    Is current system scratch
    """

    def get(self):
        """Current system is scratch"""
        if self.Get('os_root_type') == 'livecd':
            return "on" if isMount('/mnt/scratch/workspace') else "off"
        else:
            return "on" if isMount('/mnt/scratch') else "off"


class VariableOsUefiSet(ReadonlyVariable):
    """
    Current loaded system
    """

    def get(self):
        loadEfiVars()
        if device.sysfs.exists(device.sysfs.Path.Efi):
            return "on"
        else:
            return "off"


class VariableClUsedAction(ReadonlyVariable):
    """
    Переменная содержит все найденные при обработке шаблонов
    событийные переменные (ac_)
    """
    type = "table"

    def get(self):
        return []


class VariableClSystemBootSet(ReadonlyVariable):
    """
    Система находится в стадии загрузки
    """
    type = "bool"

    def get(self):
        if os.readlink('/proc/self/fd/0') == '/dev/console':
            return "on"
        else:
            return "off"


class VariableClLivePath(Variable):
    """
    Путь до образов из которых будет формировать live меню для grub
    """
    value = "/var/calculate/linux"

class VariableOsSubsystem(Variable):
    """
    Получить подтип системы (lxc, uml, openvz и т.д.)
    """

    def get(self):
        return SystemType.detect()
