# -*- 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
from os import path
from time import sleep
from calculate.lib.datavars import (Variable, VariableError,
                                    VariableInterface,
                                    ReadonlyVariable, ReadonlyTableVariable)
from calculate.lib.utils.device import (humanreadableSize, refreshUdev)
from calculate.lib.utils.files import (readLinesFile, process, getProgPath)
from calculate.install.fs_manager import FileSystemManager
from calculate.lib.utils.tools import Sizes
from itertools import *
import operator

from calculate.lib.cl_lang import setLocalTranslate, _

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


class SizeHelper(VariableInterface):
    default_size = Sizes.M

    def set(self, value):
        # convert table from value to MB
        sizeMap = {'kB': Sizes.kB,
                   'K': Sizes.K,
                   'M': Sizes.M,
                   'Mb': Sizes.Mb,
                   'G': Sizes.G,
                   'Gb': Sizes.Gb,
                   'T': Sizes.T,
                   'Tb': Sizes.Tb}
        value = value.strip()
        reSizeValue = re.compile('^(\d+)\s*(%s)?' % "|".join(sizeMap.keys()))
        res = reSizeValue.search(value)
        if not res:
            return "0"
        intValue = int(res.group(1))
        if res.group(2):
            intValue = intValue * sizeMap[res.group(2)]
        else:
            intValue = intValue * self.default_size
        return str(intValue)


MINROOTSIZE = 7 * Sizes.G


class AutopartitionError(Exception):
    """
    Autopartition error
    """
    pass


class SchemeOpt(object):
    Swap = "swap"
    Update = "update"
    UEFI = "uefi"
    LVM = "lvm"
    Calculate = "calculate"


class SchemeDevices(object):
    Swap = "swap"
    Root1 = "system1"
    Root2 = "system2"
    UEFI = "uefi"
    Data = "calculate"

    @classmethod
    def get_by_opt(cls, opt):
        opt_map = {SchemeOpt.Swap: cls.Swap,
                   SchemeOpt.UEFI: cls.UEFI,
                   SchemeOpt.Calculate: cls.Data}
        return opt_map.get(opt, opt)


class AutoPartition(object):
    """
    Autopartition maker
    """

    def recreateSpace(self, table, device, data, lvm, vgname, bios_grub,
                      bios_grub_size):
        """
        Recreate disk space by table device data lvm flag and vgname
        
        table (gpt,dos)
        device - list devices ['/dev/sda']
        data - partitions data [['/dev/calculate/boot','/boot','ext4',
                                 534234234'gpt]...]
        lvm True or False
        vgname lvm Volume Group Name
        """
        if lvm:
            self.recreateLvm(table, device, data, vgname, bios_grub,
                             bios_grub_size)
        else:
            self.recreatePartitionTable(table, device, data, bios_grub,
                                        bios_grub_size)
        refreshUdev()

    def recreatePartitionTable(self, table, device, data, bios_grub,
                               bios_grub_size):
        """
        """
        mapDispatch = {'dos': self.recreatePartitionTableDos,
                       'gpt': self.recreatePartitionTableGpt}
        if table in mapDispatch:
            mapDispatch[table](device, data, bios_grub, bios_grub_size)
        else:
            raise AutopartitionError(
                _('Autopartitioning for %s is not supported') % table)

    def recreatePartitionTableDos(self, device, data, bios_grub,
                                  bios_grub_size):
        """
        Create DOS partition table by /sbin/fdisk
        """
        NEW_PARTITION_TABLE = "o\n"
        NEW_PRIMARY_PARTITION = "n\np\n\n\n"
        NEW_EXTENDED_PARTITION = "n\ne\n\n"
        NEW_LOGICAL_PARTITION = "n\n\n"
        MAX_SIZE = "\n"
        WRITE_AND_QUIT = "w\nq\n"

        fdiskProg = getProgPath('/sbin/fdisk')
        fdisk = process(fdiskProg, device[0])
        fdisk.write(NEW_PARTITION_TABLE)
        num = 1
        for size in map(lambda x: str(Sizes().to_K(int(x))) \
                if x.isdigit() else x,
                        map(operator.itemgetter(3), data)):
            if num == 4:
                fdisk.write(NEW_EXTENDED_PARTITION + MAX_SIZE)
                num += 1
            size = MAX_SIZE if size == "allfree" else "+%sK\n" % size
            if num < 4:
                fdisk.write(NEW_PRIMARY_PARTITION + size)
            else:
                fdisk.write(NEW_LOGICAL_PARTITION + size)
            num += 1
        fdisk.write(WRITE_AND_QUIT)

        fdisk.success()
        # ожидаение появления устройств ({device}{partnum} или {device}p{partnum})
        self._waitDevice("%s%s"%(device[0],str(num - 1)),
                         "%sp%s"%(device[0],str(num - 1)))

    def recreatePartitionTableGpt(self, device, data, bios_grub=True,
                                  bios_grub_size=0):
        """
        Create GPT partition table by /sbin/gdisk
        """
        NEW_PARTITION_TABLE = "3\no\ny\n"
        NEW_PARTITION = "n\n\n\n"
        NEW_BIOSBOOT_PARTITION = "n\n\n\n%s\nef02\n"
        MAX_SIZE = "\n\n"
        WRITE_AND_QUIT = "w\ny\n"

        BIOS_BOOT_PART_NUM = 4
        BIOS_BOOT_PART_SIZE = "%dM" % (int(bios_grub_size) / Sizes.M)

        fdiskProg = getProgPath('/usr/sbin/gdisk')
        fdisk = process(fdiskProg, device[0])
        fdisk.write(NEW_PARTITION_TABLE)
        num = 1
        biosBootCreated = not bios_grub
        for size in map(lambda x: str(Sizes().to_K(int(x))) \
                if x.isdigit() else x,
                        map(operator.itemgetter(3), data)):
            if num == BIOS_BOOT_PART_NUM and not biosBootCreated:
                fdisk.write(NEW_BIOSBOOT_PARTITION %
                            ("+%s" % BIOS_BOOT_PART_SIZE))
                biosBootCreated = True
                num += 1
            if size == "allfree":
                if biosBootCreated:
                    size = MAX_SIZE
                else:
                    size = "-%s\n\n" % BIOS_BOOT_PART_SIZE
            else:
                size = "+%sK\n\n" % size
            fdisk.write(NEW_PARTITION + size)
            num += 1
        if not biosBootCreated:
            fdisk.write(NEW_BIOSBOOT_PARTITION % "")
        fdisk.write(WRITE_AND_QUIT)
        fdisk.success()
        # ожидаение появления устройств ({device}{partnum} или {device}p{partnum})
        self._waitDevice("%s%s"%(device[0],str(num - 1)),
                         "%sp%s"%(device[0],str(num - 1)))

    def _waitDevice(self, *devices):
        """
        Ожидание одного из указанных устройств
        """
        for waittime in (0.1, 0.2, 0.5, 1, 2, 4):
            if any(path.exists(x) for x in devices):
                return True
            else:
                sleep(waittime)
        raise AutopartitionError(
            _("Failed to found partition %s after creating the partition table")
            % device)

    def _createPhisicalVolumes(self, devices):
        pvCreateProg = getProgPath('/sbin/pvcreate')
        return process(pvCreateProg, "-ff", *devices).success()

    def _createVolumesGroup(self, vgname, disks):
        vgCreateProg = getProgPath('/sbin/vgcreate')
        return process(vgCreateProg, vgname, *disks).success()

    def _createLogicalVolume(self, vgname, lvname, size):
        if size.isdigit():
            size = str(Sizes().to_K(int(size)))
        if size == "allfree":
            sizeparam = "-l100%FREE"
        else:
            sizeparam = "-L%sK" % size
        lvCreateProg = getProgPath('/sbin/lvcreate')
        return process(lvCreateProg, sizeparam, vgname, "-n", lvname).success()

    def _removeVolumeGroup(self, vgname):
        vgRemoveProg = getProgPath('/sbin/vgremove')
        # double remove volume group
        return process(vgRemoveProg, vgname, "-f").success() or \
               process(vgRemoveProg, vgname, "-f").success()

    def clearLvm(self, devices, dv):
        """
        Remove lvm physical volumes from devices
        """
        vgRemoveProg = getProgPath('/sbin/vgremove')
        pvRemoveProg = getProgPath('/sbin/pvremove')
        lvRemoveProg = getProgPath('/sbin/lvremove')
        disks = dv.Select('os_disk_dev', where='os_disk_parent', _in=devices)
        failed = False
        for group, pair in groupby(dv.Select(['os_lvm_vgname', 'os_lvm_lvname'],
                                             where='os_lvm_pvname', _in=disks,
                                             sort="ASC/1"),
                                   operator.itemgetter(0)):
            for vgname, lvname in pair:
                failed |= self.doubleExec(lvRemoveProg,
                                          "%s/%s" % (vgname, lvname), "-f")
            failed |= self.doubleExec(vgRemoveProg, group, "-f")
        for pvname in list(set(disks) & set(dv.Get('os_lvm_pvname'))):
            failed |= self.doubleExec(pvRemoveProg, pvname, "-ffy")
        return not failed

    def doubleExec(self, *args):
        """
        Running double exec command with 2 seconds interval
        """
        if process(*args).failed():
            sleep(2)
            return process(*args).failed()
        return False

    def clearRaid(self, devices, dv):
        """
        Remove raid from devices
        """
        mdAdmProg = getProgPath('/sbin/mdadm')
        failed = False
        for disktype, grouped in groupby(
                dv.Select(['os_disk_type', 'os_disk_dev'],
                          where='os_disk_parent',
                          _in=['/dev/sda', '/dev/sdb'],
                          sort="ASC/1"),
                operator.itemgetter(0)):
            if disktype.endswith('-raid'):
                for disk_type, disk_dev in grouped:
                    failed |= self.doubleExec(mdAdmProg, '-S', disk_dev)
            if "raidmember" in disktype:
                for disk_type, disk_dev in grouped:
                    failed |= self.doubleExec(mdAdmProg,
                                              '--zero-superblock', disk_dev)
        return not failed

    def recreateLvm(self, table, devices, data, vgname, bios_grub,
                    bios_grub_size):
        """
        Create GPT partition table by /sbin/gdisk
        """
        notInLvm = []
        for i, device in enumerate(devices):
            if i == 0:
                DEV, MP, FS, SIZE, TABLE = 0, 1, 2, 3, 4
                notInLvm = filter(lambda x: "boot" in x[MP], data)
                self.recreatePartitionTable(table,
                                            [device], notInLvm +
                                            [['', '', '', 'allfree']],
                                            bios_grub,
                                            bios_grub_size)
            else:
                self.recreatePartitionTable(table,
                                            [device], [['', '', '', 'allfree']],
                                            bios_grub,
                                            bios_grub_size)

        lvmPartOffset = 1 + len(notInLvm)
        iPart = [lvmPartOffset] + [1] * (len(devices) - 1)
        disks = map(lambda x: "%s%d" % x, zip(devices, iPart))
        if not self._createPhisicalVolumes(disks):
            raise AutopartitionError(
                _("Failed to create physical volumes from %s")
                % ",".join(devices))

        if not self._createVolumesGroup(vgname, disks):
            raise AutopartitionError(
                _("Failed to create volume group {groupname} from {disks}")
                    .format(groupname=vgname,
                            disks=",".join(devices)))

        getProgPath('/sbin/lvcreate')
        for disk, size in map(operator.itemgetter(0, 3),
                              filter(lambda x: not "boot" in x[MP], data)):
            lvname = disk.rpartition('/')[2]
            if not self._createLogicalVolume(vgname, lvname, size):
                raise AutopartitionError(
                    _("Failed to create logical volume {name}").format(
                        name=lvname))
        self._waitDevice('/dev/%s/%s' % (vgname, SchemeDevices.Root1))


class AutopartitionHelper(VariableInterface):
    """
    Helper for autopartiton device and mount point creating
    """

    def deviceOpts(self, listvalue):
        l = [x for x in listvalue if x not in (SchemeOpt.LVM,)]
        for i in (SchemeOpt.UEFI, SchemeOpt.Swap):
            if i in l:
                l.remove(i)
                yield SchemeDevices.get_by_opt(i)
                break
        yield SchemeDevices.Root1
        if SchemeOpt.Update in l:
            l.remove(SchemeOpt.Update)
            yield SchemeDevices.Root2
        for i in l:
            yield SchemeDevices.get_by_opt(i)

    def bindOpts(self, listvalue):
        return filter(lambda x: x in (SchemeOpt.Calculate,),
                      listvalue)

    def mpByOpts(self, value):
        mapMp = {SchemeDevices.Swap: 'swap',
                 SchemeDevices.Root1: '/',
                 SchemeDevices.Root2: '',
                 SchemeDevices.UEFI: '/boot/efi',
                 SchemeDevices.Data: '/var/calculate'}
        return mapMp.get(value, '')

    def sourceMpByOpts(self, value):
        mapMp = {SchemeOpt.Calculate: '/var/calculate/home'}
        return mapMp.get(value, '')

    def targetMpByOpts(self, value):
        mapMp = {SchemeOpt.Calculate: '/home'}
        return mapMp.get(value, '')

    def getAutopartitionScheme(self):
        """
        Получить список опций отсортированный в нужном порядке
        """
        optOrder = {SchemeOpt.UEFI: 0,
                    SchemeOpt.Update: 1,
                    SchemeOpt.Swap: 2,
                    SchemeOpt.Calculate: 3,
                    }
        return sorted(self.Get('cl_autopartition_scheme'), key=optOrder.get)

    def uncompatible(self):
        if self.Get('cl_autopartition_set') == "off":
            return _("Autopartition options are not available with manual "
                     "partitioning")
        return ""


class VariableHrMemorySize(ReadonlyVariable):
    """
    Memory size in bytes
    """
    type = "int"

    def get(self):
        reMemTotal = re.compile(r'^MemTotal:\s*(\d+)\s*kB$')
        totalMemList = filter(lambda x: x,
                              map(reMemTotal.search,
                                  readLinesFile('/proc/meminfo')))
        if totalMemList:
            size = int(totalMemList[0].group(1)) * Sizes.K
            return str(size)
        return "0"

    def humanReadable(self):
        return humanreadableSize(self.Get())


class VariableClAutopartitionSwapSize(SizeHelper, AutopartitionHelper,
                                      Variable):
    """
    Swap size
    """
    type = "size-m"
    opt = ["--swap-size"]
    metavalue = "SIZE"
    untrusted = True

    def init(self):
        self.label = _("Swap partition size") + " (MB)"
        self.help = _("set the swap partition size for autopartition")

    def get(self):
        size = int(self.Get('hr_memory_size'))
        if size < Sizes.G:
            size = Sizes.G
        return str(size)

    def humanReadable(self):
        return humanreadableSize(int(self.Get()))


class VariableClAutopartitionDevice(AutopartitionHelper, Variable):
    """
    Device for autopartition
    """
    type = "choice-list"
    element = "selecttable"
    opt = ["-D"]
    metavalue = "DEVICE"
    untrusted = True

    def init(self):
        self.help = _("set the device for autopartition")
        self.label = _("Devices for install")

    def get(self):
        choiceVal = map(lambda x: x[0], self.choice())
        if len(choiceVal) == 1:
            return [choiceVal[0]]
        return []

    def choice(self):
        deviceParentMap = dict(self.ZipVars('os_device_dev', 'os_device_name'))
        return map(lambda x: (x, "%s (%s)" % (x,
                                              deviceParentMap.get(x, _(
                                                  "Unknown")))),
                   self.Get('os_device_dev'))

    def checkNeeded(self, valuelist, usedDevices, agregationType):
        needDevices = list(set(usedDevices) - set(valuelist))
        if needDevices:
            raise VariableError(_("Disks {selecteddisk} are part of "
                                  "{agrtype}\nYou need to use {needdisk} as well or "
                                  "clear {agrtype} manually")
                .format(selecteddisk=",".join(
                list(set(usedDevices) & set(valuelist))),
                needdisk=",".join(needDevices),
                agrtype=agregationType))

    def checkOnLvm(self, valuelist):
        disks = self.Select('os_disk_dev',
                            where='os_disk_parent', _in=valuelist)
        vgroups = self.Select('os_lvm_vgname',
                              where='os_lvm_pvname', _in=disks)
        lvmDisks = self.Select('os_lvm_pvname',
                               where='os_lvm_vgname', _in=vgroups)
        lvmDevices = self.Select('os_disk_parent', where='os_disk_dev',
                                 _in=lvmDisks)
        self.checkNeeded(valuelist, lvmDevices, "LVM")

    def checkOnRaid(self, valuelist):
        disks = self.Select('os_disk_dev',
                            where='os_disk_parent', _in=valuelist)
        raids = filter(None, self.Select('os_disk_raid',
                                         where='os_disk_dev', _in=disks))
        raidDisks = self.Select('os_disk_dev', where='os_disk_raid', _in=raids)
        raidDevices = self.Select('os_disk_parent',
                                  where='os_disk_dev',
                                  _in=raidDisks)
        self.checkNeeded(valuelist, raidDevices, "RAID")

    def check(self, valuelist):
        if self.Get('cl_autopartition_set') == "on":
            if not valuelist:
                raise VariableError(
                    _("For autopartition, please select the device"))
            useDisks = self.Select('os_disk_parent',
                                   where='os_disk_mount', ne='')
            for value in valuelist:
                if value in useDisks:
                    raise VariableError(
                        _("Device %s is already in use by the current "
                          "system") % value)
            self.checkOnLvm(valuelist)
            self.checkOnRaid(valuelist)
            if len(valuelist) > 1 and \
                            self.Get('cl_autopartition_lvm_set') == 'off':
                raise VariableError(
                    _("You should use LVM to install on more that one device"))
            freeSize = int(self.Get('cl_autopartition_free_size'))
            if freeSize < 0 and (abs(freeSize)) / (Sizes.M * 100) > 0:
                availSize = int(self.Get('cl_autopartition_device_size'))
                raise VariableError(
                    _("There is not enough space on this device") + "\n" +
                    _("{avail} available, {need} needed").format(
                        avail=humanreadableSize(availSize),
                        need=humanreadableSize(availSize - freeSize)))


class VariableClAutopartitionSet(Variable):
    """
    Using autopartition
    """
    type = "bool"
    value = "off"
    element = "radio"
    opt = ["--autopartition", "-p"]

    def init(self):
        self.label = _("Partitions")
        self.help = _("autopartition")

    def choice(self):
        return [("off", _("Use the current partitions")),
                ("on", _("Autopartition"))]


class VariableClAutopartitionBriefSet(VariableClAutopartitionSet):
    def get(self):
        return self.Get('cl_autopartition_set')

    def uncompatible(self):
        if self.Get('os_install_root_type') == 'flash':
            return _("This option not used for Flash install")


class VariableClAutopartitionScheme(AutopartitionHelper, AutoPartition,
                                    Variable):
    """
    Autopartition scheme
    """
    type = "choice-list"
    element = "selecttable"
    opt = ["--auto-scheme", "-S"]
    metavalue = "AUTOPARTOPTS"
    check_after = ["cl_autopartition_table"]

    def init(self):
        self.help = _("autopartition options")
        self.label = _("Autopartition options")

    def get(self):
        if self.Get('os_uefi_set') == 'on':
            return [SchemeOpt.UEFI,
                    SchemeOpt.Swap,
                    SchemeOpt.Update,
                    SchemeOpt.Calculate]
        elif self.Get('cl_autopartition_table') == 'gpt':
            return [SchemeOpt.Swap,
                    SchemeOpt.Update,
                    SchemeOpt.Calculate]
        else:
            return [SchemeOpt.Swap,
                    SchemeOpt.Update,
                    SchemeOpt.Calculate]

    def choice(self):
        return [
            (SchemeOpt.Swap, _("Swap partition")),
            (SchemeOpt.Update, _("The partition for the update")),
            (SchemeOpt.Calculate, _("/var/calculate partition")),
            (SchemeOpt.UEFI, _("Use the UEFI bootloader")),
            (SchemeOpt.LVM, _("Use LVM")),
        ]

    def check(self, value):
        if SchemeOpt.UEFI in value:
            if self.Get('os_uefi_set') == 'off':
                raise VariableError(
                    _("Your system must be loaded in UEFI for using this "
                      "bootloader"))
            if self.Get('os_install_arch_machine') != 'x86_64':
                raise VariableError(
                    _("Architecture of the target system must be x86_64 "
                      "for using the UEFI bootloader"))
            if self.Get('cl_autopartition_table') != 'gpt':
                raise VariableError(
                    _("The partition table must be GPT for using "
                      "UEFI bootloader"))


class VariableClAutopartitionRootSizeDefault(Variable):
    """
    Размер root раздела при авторазметке
    """
    value = str(Sizes.G * 15)


class VariableClAutopartitionRootSizeMin(Variable):
    """
    Минимальнй размер root раздела
    """
    value = str(Sizes.G * 7)


class VariableClAutopartitionRootSize(SizeHelper, AutopartitionHelper,
                                      Variable):
    """
    Root partition size for autopartition
    """
    type = "size-m"
    opt = ["--root-size"]
    metavalue = "SIZE"
    untrusted = True

    def init(self):
        self.label = _("Root partition size") + " (MB)"
        self.help = _("set the root partition size for autopartition")

    def get(self):
        size = int(self.Get('cl_autopartition_root_size_default'))
        deviceSize = int(self.Get('cl_autopartition_device_size'))
        minRootSize = int(self.Get('cl_autopartition_root_size_min'))
        if size >= deviceSize:
            size = max(deviceSize, minRootSize)
        return str(size)

    def check(self, value):
        minRootSize = int(self.Get('cl_autopartition_root_size_min'))
        if (self.Get('cl_autopartition_device') and
                    self.Get('cl_autopartition_set') == "on"):
            if int(value) < minRootSize:
                raise VariableError(
                    _("The root partition should be at least {size}").format(
                        size="%s Gb" % (Sizes().to_G(minRootSize))))

    def humanReadable(self):
        return humanreadableSize(int(self.Get()))


class VariableClAutopartitionTable(AutopartitionHelper, Variable):
    """
    Partition table for autopartition
    """
    type = "choice"
    value = "gpt"
    opt = ["--partition-table", "-T"]
    metavalue = "TABLE"

    def init(self):
        self.label = _("Partition table")
        self.help = _("set the partition table for autopartition")

    def choice(self):
        return [("dos", "DOS-type Partition Table"),
                ("gpt", "GUID Partition Table (GPT)")]


class VariableClAutopartitionDiskData(ReadonlyTableVariable):
    """
    New partition data for autopart device
    """
    source = ['cl_autopartition_disk_dev',
              'cl_autopartition_disk_mount',
              'cl_autopartition_disk_format',
              'cl_autopartition_disk_size',
              'cl_autopartition_disk_part',
              'cl_autopartition_disk_scheme']


class VariableClAutopartitionBiosGrubSet(ReadonlyVariable):
    """
    Create bios_grub partition
    """
    type = "bool"

    def get(self):
        if (self.GetBool('cl_autopartition_set') and
                    self.Get('cl_autopartition_table') == 'gpt'):
            return "on"
        return "off"


class VariableClAutopartitionLvmSet(ReadonlyVariable):
    """
    Using LVM for autopartition
    """
    type = "bool"

    def get(self):
        return ("on" if SchemeOpt.LVM in self.Get('cl_autopartition_scheme')
                else "off")


class VariableClAutopartitionUefiSet(ReadonlyVariable):
    """
    Using UEFI bootloader
    """
    type = "bool"

    def get(self):
        return ("on" if SchemeOpt.UEFI in self.Get('cl_autopartition_scheme')
                else "off")


class VariableClAutopartitionLvmVgname(Variable):
    """
    Volume group name for LVM autopartition
    """

    def get(self):
        def generateName(startName):
            yield startName
            for i in count(20):
                yield "%s%d" % (startName, i)

        for name in generateName("calculate"):
            disks = self.Select('os_lvm_pvname', where='os_lvm_vgname', eq=name)
            devices = self.Select('os_disk_parent',
                                  where='os_disk_dev', _in=disks)
            if set(devices) <= set(self.Get('cl_autopartition_device')):
                return name


class VariableClAutopartitionDiskDev(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition virtual disk on device
    """
    type = "list"

    def generateDisks(self, dos=False, devices=(), scheme=(), number=0):
        """
        Generate disks for automount scheme
        """
        number = 1
        if self.Select('install.os_device_array_set',
            where='install.os_device_dev', eq=devices[0],limit=1) == 'on':
            pattern = "%sp%d"
        else:
            pattern = "%s%d"
        for line in self.deviceOpts(scheme):
            # for dos 4 - extension
            # for gpt 4 - for bios_boot
            if (number == 4
                and (self.Get(
                    'cl_autopartition_bios_grub_set') == 'on' or dos)):
                number += 1
            yield pattern % (devices[0], number)
            number += 1

    def generateLvm(self, scheme=(), devices=(), **kwargs):
        vgname = self.Get('cl_autopartition_lvm_vgname')
        number = 1
        for line in self.deviceOpts(scheme):
            if line in (SchemeDevices.UEFI,):
                if (number == 4 and
                            self.Get('cl_autopartition_bios_grub_set') == 'on'):
                    number += 1
                yield "%s%d" % (devices[0], number)
                number += 1
            else:
                yield "/dev/%s/%s" % (vgname, line)

    def diskGenerator(self):
        if self.Get('cl_autopartition_lvm_set') == 'on':
            return self.generateLvm
        else:
            return self.generateDisks

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            devices = self.Get('cl_autopartition_device')
            if devices:
                res = list(self.diskGenerator()(
                    devices=devices,
                    scheme=scheme,
                    dos=self.Get('cl_autopartition_table') == 'dos'))
                return res
        return []


class VariableClAutopartitionDiskScheme(AutopartitionHelper, ReadonlyVariable):
    """
    Назначение раздела относительно схемы авторазметки
    """
    type = "list"

    def generateScheme(self, scheme):
        """
        Generate scheme for automount scheme
        """
        for line in self.deviceOpts(scheme):
            yield line

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            device = self.Get('cl_autopartition_device')
            if device:
                return list(self.generateScheme(scheme))
        return []


class VariableClAutopartitionDiskMount(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition mount points
    """
    type = "list"

    def generateMounts(self, scheme):
        """
        Generate mount points for automount scheme
        """
        for line in self.deviceOpts(scheme):
            yield self.mpByOpts(line)

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            device = self.Get('cl_autopartition_device')
            if device:
                return list(self.generateMounts(scheme))
        return []


class VariableClAutopartitionDiskFormat(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition disk filesystem
    """
    type = "list"

    def generateFormat(self, scheme):
        """
        Generate filesystems for automount scheme
        """
        for line in self.deviceOpts(scheme):
            if line == SchemeDevices.Swap:
                yield "swap"
            elif line == SchemeDevices.UEFI:
                yield "vfat"
            else:
                yield FileSystemManager.defaultFS['hdd']

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            device = self.Get('cl_autopartition_device')
            if device:
                return list(self.generateFormat(scheme))
        return []


class VariableClAutopartitionDiskPart(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition partition type (primary,extended,logical,gpt)
    """
    type = "list"

    def generatePart(self, scheme, dos):
        """
        Generate part type for automount scheme
        """
        number = 1
        for line in self.deviceOpts(scheme):
            if dos:
                if number < 4:
                    yield "primary"
                else:
                    yield "logical"
                number += 1
            else:
                yield "gpt"

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            table = self.Get('cl_autopartition_table')
            if scheme:
                return list(self.generatePart(scheme, table == 'dos'))
        return []


class VariableClAutopartitionDiskType(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition partition scheme (simple - disk-partition)
    """
    type = "list"

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            if self.Get('cl_autopartition_lvm_set') == "on":
                diskType = "LVM"
            else:
                diskType = "disk-partition"
            return [diskType] * len(self.Get('cl_autopartition_disk_dev'))
        return []


class VariableClAutopartitionDiskSize(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition disk size
    """
    type = "list"

    def generateSize(self, scheme, memory, bootsize, uefisize, rootsize,
                     availsize, minrootsize):
        args = {SchemeDevices.Swap: memory,
                SchemeDevices.Root1: rootsize,
                SchemeDevices.Root2: rootsize,
                SchemeDevices.UEFI: uefisize,
                }
        minrootsize = int(minrootsize)
        deviceOpts = list(self.deviceOpts(scheme))
        for line in deviceOpts[:-1]:
            availsize -= int(args.get(line, 0))
            yield str(args.get(line, 0))
        if deviceOpts[-1] in (SchemeDevices.Root1,
                              SchemeDevices.Root2) and availsize < minrootsize:
            yield str(minrootsize)
        elif availsize < 1 * Sizes.G:
            yield str(1 * Sizes.G)
        else:
            yield "allfree"

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            device = self.Get('cl_autopartition_device')
            availSize = self.Get('cl_autopartition_device_size')
            if device:
                return list(self.generateSize(
                    scheme,
                    self.Get('cl_autopartition_swap_size'),
                    self.Get('cl_autopartition_boot_size'),
                    self.Get('cl_autopartition_uefi_size'),
                    self.Get('cl_autopartition_root_size'),
                    int(availSize),
                    self.Get('cl_autopartition_root_size_min')))
        return []

    def humanReadable(self):
        allSize = self.Get('cl_autopartition_free_size')
        return map(humanreadableSize,
                   map(lambda x: allSize if x == "allfree" else x, self.Get()))


class VariableClAutopartitionUefiSize(Variable):
    """
    Size of EF00 partition
    """
    value = str(200 * Sizes.M)


class VariableClAutopartitionBootSize(Variable):
    """
    Size of boot partition
    """
    value = str(512 * Sizes.M)


class VariableClAutopartitionBiosGrubSize(Variable):
    """
    Размер раздела bios_grub для авторазметки
    """
    value = str(10 * Sizes.M)


class VariableClAutopartitionDeviceSize(ReadonlyVariable):
    """
    Available devices size
    """

    def get(self):
        devices = self.Get('cl_autopartition_device')
        if not devices:
            return '0'
        sizeDevice = map(int, self.Select('os_device_size',
                                          where='os_device_dev',
                                          _in=devices))
        # TODO: remove set 10G
        # return str(1024*1024*1024*10)
        return str(reduce(operator.add, sizeDevice, 0))


class VariableClAutopartitionFreeSize(ReadonlyVariable):
    """
    Freesize of device with current root_size and memory
    """
    type = "int"

    def get(self):
        sizeDevice = int(self.Get('cl_autopartition_device_size'))
        sizes = self.Get('cl_autopartition_disk_size')
        return str(reduce(lambda x, y: (x - int(y)) if y.isdigit() else x,
                          sizes, int(sizeDevice)))


class VariableClAutopartitionBindData(ReadonlyTableVariable):
    """
    Autopartition bind data
    """
    source = ['cl_autopartition_bind_path',
              'cl_autopartition_bind_mountpoint']


class VariableClAutopartitionBindPath(AutopartitionHelper, ReadonlyVariable):
    """
    Autopartition bind points
    """
    type = "list"

    def generatePath(self, scheme):
        for line in self.bindOpts(scheme):
            yield self.sourceMpByOpts(line)

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            device = self.Get('cl_autopartition_device')
            return list(self.generatePath(scheme))
        return []


class VariableClAutopartitionBindMountpoint(AutopartitionHelper,
                                            ReadonlyVariable):
    """
    Autopartition bind points
    """
    type = "list"

    def generateMountPoint(self, scheme):
        for line in self.bindOpts(scheme):
            yield self.targetMpByOpts(line)

    def get(self):
        if self.Get('cl_autopartition_set') == "on":
            scheme = self.getAutopartitionScheme()
            device = self.Get('cl_autopartition_device')
            return list(self.generateMountPoint(scheme))
        return []
