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

# Copyright 2008-2012 Calculate Ltd. 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
import time
from os import path

from calculate.lib.cl_lang import setLocalTranslate
setLocalTranslate('cl_lib3',sys.modules[__name__])

def getUUIDDict(revers=False,devs=[]):
    """Get dict UUID -> dev"""
    blkidProcess = process("/sbin/blkid","-s","UUID","-c","/dev/null",*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,readFile,process)
from common import getpathenv

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=[]):
    """Get lvm partitions"""
    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"""
    prop = getUdevDeviceInfo(path=syspath)
    # real device
    if prop.get('ID_TYPE',"") == "disk" and \
       prop.get('DEVTYPE',"") == "disk":
        return prop.get('DEVNAME',"")
    # partition
    if prop.get('DEVTYPE') == "partition":
        return getPartitionDevice(path.dirname(syspath))
    # 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 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:
    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 not keyCache 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"))))
            tm = time.time()
            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 = 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 detectBuild(pathname,dictInfo):
    """Detect build by root passwd 'root'"""
    shadowPath = pathJoin(pathname,'/etc/shadow')
    if r"root:$1$JMvNh5xg$VnV1DyJdTcwuZ0hp5YiJG0:14349:0:::::" in \
        readFile(shadowPath):
        dictInfo['type'] = ' assemble'
    elif path.exists(pathJoin(pathname,"delta")) and \
         path.exists(pathJoin(pathname,"workspace")):
        dictInfo['type'] = " builder"
        issue = readFile(pathJoin(pathname,'etc/gentoo-release'))
        if "Server" in issue:
            if "Scratch" in issue:
                dictInfo['name'] = "CSS"
            else:
                dictInfo['name'] = "CDS"
        elif "Desktop" in issue:
            if "XFCE" in issue:
                dictInfo['name'] = "CLDX"
            elif "KDE" in issue:
                dictInfo['name'] = "CLD"
            elif "GNOME" in issue:
                dictInfo['name'] = "CLDG"
        elif "Scratch" in issue:
            dictInfo['name'] = "CLS"
    else:
        dictInfo['type'] = ''
    return dictInfo

def getOsProberHash(getContentFunc=None):
    """Get partition content by os-prober"""
    os_prober = getProgPath('/usr/bin/os-prober')
    if os_prober:
        DEV,LONG,SHORT,TYPE = 0,1,2,3
        osProberList = \
            map(lambda x:[getUdevDeviceInfo(name=x[DEV]).get('DEVNAME',''),
                          x[LONG],x[SHORT],x[TYPE]],
            filter(lambda x:len(x)>=4,
            map(lambda x:x.split(":"),
            process(os_prober))))
        for osRecord in osProberList:
            if "Gentoo" in osRecord[SHORT] and getContentFunc:
                osDescr = getContentFunc(osRecord[DEV],addFunc=detectBuild)
                if "name" in osDescr and "march" in osDescr and \
                   "build" in osDescr and "ver" in osDescr and \
                   (osDescr["ver"] != "0" or osDescr["build"]):
                    if osDescr['build']:
                        osDescr['build'] = "-%s"%osDescr['build']
                    else:
                        osDescr['build'] = "-%s"%osDescr['ver']
                    osRecord[SHORT] = \
                        "{name}-{march}{build}{type}".format(**osDescr)
                else:
                    osRecord[SHORT] = "Gentoo"
            elif "Gentoo" in osRecord[SHORT] and "Calculate" in osRecord[LONG]:
                osRecord[SHORT] = "Calculate"
        osProberHash = \
            dict(
            map(lambda x:(x[DEV],x[SHORT]),
            osProberList))
    else:
        osProberHash = {}
    return osProberHash

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():
            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:
            pass
        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)
