# -*- 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 sys
import re
import os
from os import path

from calculate.lib.cl_lang import setLocalTranslate

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

# используется для кэша функции getLvmPartitions()
_lvm_partitions_cache = []


def getUUIDDict(revers=False, devs=()):
    """Get dict UUID -> dev"""
    blkidProcess = process("/sbin/blkid", "-s", "UUID", "-c", "/dev/null",
                           *list(devs))
    if revers:
        datafunc = lambda x, y: (y, x)
    else:
        datafunc = lambda x, y: (x, y)
    DEV, UUID = 0, 1
    reSplit = re.compile('^([^:]+):.*UUID="([^"]+)"', re.S)
    return dict(
        map(lambda x: datafunc("UUID=%s" % x[UUID],
                               getUdevDeviceInfo(name=x[DEV]).get('DEVNAME',
                                                                  x[DEV])),
            map(lambda x: x.groups(),
                filter(lambda x: x,
                       map(reSplit.search,
                           blkidProcess)))))


from files import (getProgPath, pathJoin, listDirectory,
                   checkUtils, process)


def detectDeviceForPartition(dev):
    """Detect parent device for partition by udev and return property"""
    prop = getUdevDeviceInfo(name=dev)
    if prop.get('DEVTYPE', '') != 'partition':
        return ''
    parentpath = path.dirname(prop.get('DEVPATH', ''))
    if parentpath:
        devProp = getUdevDeviceInfo(path=parentpath)
        return devProp.get('DEVNAME', '')
    return None


def countPartitions(devname):
    """Count partition for specified device"""
    syspath = getUdevDeviceInfo(name=devname).get('DEVPATH', '')
    if not syspath:
        return 0
    deviceName = path.basename(syspath)
    if not syspath.startswith("/sys"):
        syspath = pathJoin("/sys", syspath)
    return len(filter(lambda x: x.startswith(deviceName),
                      listDirectory(syspath)))


def getLvmGroups():
    """Get LVM groups"""
    pvdisplayCmd = getProgPath('/sbin/pvdisplay')
    pvdata = process(pvdisplayCmd, "-C", "-o", "vg_name", "--noh")
    return filter(lambda x: x, pvdata.read().split())


def getLvmPartitions(vg_name, lv_name, cache=None):
    """Get lvm partitions"""
    if cache is None:
        cache = _lvm_partitions_cache
    if not cache:
        pvdisplayCmd = getProgPath('/sbin/pvdisplay')
        pvdata = process(pvdisplayCmd, "-C", "-o",
                         "vg_name,lv_name,pv_name", "--noh")
        if pvdata.success():
            cache.extend(filter(
                lambda x: x and len(x) == 3,
                map(lambda x: x.split(),
                    pvdata.read().split('\n'))))
    if cache:
        res = map(lambda x: x[2],
                  filter(lambda x: x[0] == vg_name and x[1] == lv_name, cache))
        if res:
            return res
    return []


def getPartitionDevice(syspath):
    """Get real parent device by partition,lvm,mdraid"""
    if not syspath.startswith('/sys'):
        syspath = pathJoin('/sys', syspath)
    prop = getUdevDeviceInfo(path=syspath)
    # real device
    if (prop.get('DEVTYPE', "") == "disk" and
            (prop.get('ID_TYPE', "") == "disk" or
                 path.exists(path.join(syspath, "device")))):
        return prop.get('DEVNAME', "")
    # partition
    if prop.get('DEVTYPE') == "partition":
        return getPartitionDevice(path.dirname(syspath))
    # md raid
    if prop.get('MD_LEVEL', "").startswith("raid"):
        syspath = pathJoin(syspath, "md")
        for rd in filter(lambda x: path.basename(x).startswith('rd'),
                         listDirectory(syspath, fullPath=True)):
            rdBlockPath = path.join(rd, "block")
            if path.exists(rdBlockPath):
                return getPartitionDevice(path.realpath(rdBlockPath))
        else:
            return ""
    # lvm
    if prop.get('DM_LV_NAME', "") != "":
        parts = getLvmPartitions(prop.get('DM_VG_NAME', ''),
                                 prop.get('DM_LV_NAME', ''))
        if parts:
            propPartLvm = getUdevDeviceInfo(name=parts[0])
            if 'DEVPATH' in propPartLvm:
                return getPartitionDevice(propPartLvm['DEVPATH'])
    return ""


def lspci(filtername=None, shortInfo=False):
    """Get hash of lspci, filtred by filtername. If shortInfo, then
    type,vendor and name get only first word

    pcidata(domain,bus,slot,func)
    'type'
    'vendor'
    'name'"""
    reData = re.compile(r'(\S+)\s"([^"]+)"\s+"([^"]+)"\s+"([^"]+)"', re.S)
    if filtername:
        if hasattr(filtername, '__call__'):
            filterfunc = filtername
        else:
            filterfunc = lambda x: filtername in x
    else:
        filterfunc = lambda x: x
    if shortInfo:
        sfunc = lambda x: x.partition(" ")[0]
    else:
        sfunc = lambda x: x
    lspciProg = checkUtils('/usr/sbin/lspci')
    processLsPci = process(lspciProg, "-m")
    retData = {}
    for device in map(lambda x: x.groups(),
                      filter(lambda x: x,
                             map(reData.search,
                                 filter(filterfunc,
                                        processLsPci)))):
        retData[device[0]] = {'type': sfunc(device[1]),
                              'vendor': sfunc(device[2]),
                              'name': sfunc(device[3])}
    return retData


class getUdevDeviceInfo(object):
    cache = {}
    udevadmCmd = ""

    def clearCache(self):
        self.cache = {}

    def __call__(self, path="", name=""):
        """Get device info by syspath of name"""
        typeQuery = "--name" if name else "--path"
        value = name or os.path.realpath(path)
        keyCache = "%s=%s" % (typeQuery, value)
        if not self.cache:
            refreshUdev(onlyTrigger=True)
        if keyCache not in self.cache:
            if not self.udevadmCmd:
                self.udevadmCmd = getProgPath('/sbin/udevadm')
            data = dict(filter(
                lambda x: x[0],
                map(lambda x: x.partition("=")[0::2],
                    process(self.udevadmCmd, "info", "--query",
                            "property",
                            typeQuery, value).read().split("\n"))))
            keyNameCache = data.get('DEVNAME', '')
            keyPathCache = data.get('DEVPATH', '')
            if keyNameCache:
                keyNameCache = "--name=" + keyNameCache
            if keyPathCache:
                keyPathCache = "--path=/sys" + keyPathCache
            if "DEVNAME" in data and not keyNameCache in self.cache:
                self.cache[keyNameCache] = data
            if "DEVPATH" in data and not keyPathCache in self.cache:
                self.cache[keyPathCache] = data
            return data
        else:
            # print "fromCache",keyCache
            return self.cache[keyCache]


getUdevDeviceInfo = getUdevDeviceInfo()


def convertNameToSyspath(name):
    dev = getUdevDeviceInfo(name=name).get('DEVPATH', "")
    if dev and not dev.startswith('/sys'):
        dev = pathJoin('/sys', dev)
    return dev


def humanreadableSize(size):
    """
    Human readable size (1024 -> 1K and etc)
    """
    try:
        size = int(size)
    except ValueError:
        return ""
    suffix = (((1024 ** 0), "", False),
              ((1024 ** 1), "K", False),
              ((1024 ** 2), "M", False),
              ((1024 ** 3), "G", True),
              ((1024 ** 4), "T", True),
              ((1024 ** 5), "P", True))
    suffix = filter(lambda x: size > x[0], suffix)
    if suffix:
        suffix = suffix[-1]
        printSize = int(size / (float(suffix[0]) / 10))
        printSizeTail = printSize % 10
        printSize /= 10
        if suffix[2] and printSizeTail:
            return "%d.%d%s" % (printSize, printSizeTail, suffix[1])
        else:
            return "%d%s" % (printSize, suffix[1])
    return str(size)


def getPartitionSize(syspath=None, name=None, inBytes=False):
    """
    Get partition size
    """
    if name:
        dev = convertNameToSyspath(name)
    else:
        dev = syspath
    SECTORSIZE = 512
    sizeFile = pathJoin(dev, "size")
    if path.exists(sizeFile):
        size = int(open(sizeFile, 'r').read().strip()) * SECTORSIZE
        if inBytes:
            return str(size)
        else:
            return humanreadableSize(size)
    return ""


def getDeviceType(syspath=None, name=None):
    """Get device type (disk,partition,lvm,raid)"""
    if name:
        prop = getUdevDeviceInfo(name=name)
        syspath = prop.get('DEVPATH', '')
    else:
        prop = getUdevDeviceInfo(path=syspath)
    # real device
    if prop.get('ID_CDROM', ""):
        return "cdrom"
    if (prop.get('ID_TYPE', "") == "disk" and
                prop.get('DEVTYPE', "") == "disk"):
        return "disk"
    # partition
    if prop.get('DEVTYPE') == "partition":
        return getDeviceType(path.dirname(syspath)) + "-partition"
    # md raid
    if prop.get('MD_LEVEL', "").startswith("raid"):
        if not syspath.startswith('/sys'):
            syspath = pathJoin('/sys', syspath)
        syspath = pathJoin(syspath, "md")
        for rd in filter(lambda x: path.basename(x).startswith('rd'),
                         listDirectory(syspath, fullPath=True)):
            rdBlockPath = path.join(rd, "block")
            if path.exists(rdBlockPath):
                return getDeviceType(path.realpath(rdBlockPath)) + "-raid"
        else:
            return "loop"
    # lvm
    if prop.get('DM_LV_NAME', "") != "":
        parts = getLvmPartitions(prop.get('DM_VG_NAME', ''),
                                 prop.get('DM_LV_NAME', ''))
        if parts:
            propPartLvm = getUdevDeviceInfo(name=parts[0])
            if 'DEVPATH' in propPartLvm:
                return getDeviceType(propPartLvm['DEVPATH']) + "-lvm"
    return "loop"


def getRaidPartitions(raidpath):
    """Get raid partitions"""
    prop = getUdevDeviceInfo(path=raidpath)
    raidParts = []
    if prop.get('MD_LEVEL', "").startswith("raid"):
        if not raidpath.startswith('/sys'):
            raidpath = pathJoin('/sys', raidpath)
        raidpath = pathJoin(raidpath, "md")
        for rd in filter(lambda x: path.basename(x).startswith('rd'),
                         listDirectory(raidpath, fullPath=True)):
            rdpath = path.join(raidpath, rd, "block")
            if path.exists(rdpath):
                raidParts.append(
                    getUdevDeviceInfo(path=path.realpath(rdpath)).get(
                        "DEVNAME", ''))
    return filter(lambda x: x, raidParts)


def getPartitionType(prop):
    """Get type of dos part table (primary,extended or logical)"""
    if prop.get('ID_PART_ENTRY_SCHEME') == 'dos':
        partId = prop.get('ID_PART_ENTRY_TYPE', '')
        partNumber = prop.get('ID_PART_ENTRY_NUMBER', '')
        if partId and partNumber:
            if partId == "0x5":
                return "extended"
            elif int(partNumber) > 4:
                return "logical"
            else:
                return "primary"
    return prop.get('ID_PART_TABLE_TYPE', '')


def refreshLVM():
    """Run command which refresh information about LVM"""
    vgscan = getProgPath('/sbin/vgscan')
    vgchange = getProgPath('/sbin/vgchange')
    lvchange = getProgPath('/sbin/lvchange')

    if vgscan and vgchange and lvchange:
        process(vgscan).success()
        process(vgchange, '-ay').success()
        process(vgchange, '--refresh').success()
        for group in getLvmGroups():
            if not os.environ.get('EBUILD_PHASE'):
                process(lvchange, '-ay', group).success()
                process(lvchange, '--refresh', group).success()


def loadEfiVars():
    """
    Load module with efivars
    """
    modprobe = getProgPath('/sbin/modprobe')
    if not path.exists('/sys/firmware/efi') and modprobe:
        process(modprobe, 'efivars').success()


def refreshUdev(onlyTrigger=False):
    """Run command which refresh information about device in udev"""
    if not onlyTrigger:
        getUdevDeviceInfo.clearCache()
    udevadm = getProgPath('/bin/udevadm')
    if udevadm:
        blkidFile = '/etc/blkid.tab'
        try:
            if path.exists(blkidFile):
                os.unlink(blkidFile)
        except OSError:
            pass
        if not os.environ.get('EBUILD_PHASE'):
            process(udevadm, "trigger", "--subsystem-match", "block").success()
            process(udevadm, "settle", "--timeout=15").success()


def getCommonDeviceName(dev):
    """
    Get common device name (example: LVM -> /dev/dm- and etc)
    """
    return getUdevDeviceInfo(name=dev).get('DEVNAME', dev)
