# -*- 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 calculate.lib.datavars import (Variable, VariableError, FieldValue,
                                    VariableInterface, HumanReadable,
                                    ReadonlyVariable, ReadonlyTableVariable)
import calculate.lib.utils.device as device
from calculate.lib.utils.device import humanreadableSize
from calculate.lib.utils import partition
from calculate.lib.utils.files import (readLinesFile)
from calculate.lib.utils.mount import isMount, try_umount
from calculate.install.fs_manager import FileSystemManager
from calculate.lib.utils.tools import Sizes, traverse
from itertools import *

from calculate.lib.cl_lang import setLocalTranslate, _
from calculate.lib.variables.system import RootType

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)


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


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


class AutopartitionHelper(VariableInterface):
    """
    Helper for autopartiton device and mount point creating
    """
    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 check(self, value):
        if value.isdigit() and int(value) <= Sizes.M:
            raise VariableError(
                _("Swap size should not be less that 1MiB"))

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


class VariableClAutopartitionDeviceData(ReadonlyTableVariable):
    """
    Список устройств, которые могут выбраны в качестве дисков под разметку
    """

    source = ['cl_autopartition_device_dev',
              'cl_autopartition_device_type',
              'cl_autopartition_device_size',
              'cl_autopartition_device_name']

    re_raid = re.compile("raid[1-9]")

    def get(self, hr=HumanReadable.No):
        def generator():
            for dev, _type, fulltype, size, name in self.ZipVars(
                    'os_device_dev', 'os_device_type',
                    'os_device_fulltype',
                    'os_device_size', 'os_device_name'):
                if not _type.startswith('raid'):
                    _type = "device"
                    if not name:
                        name = _("Unknown")
                else:
                    name = _type.upper()
                    if self.re_raid.search(fulltype):
                        _type = "raid"
                    else:
                        _type = "raid0"

                yield [dev, _type, size, name]

        return list(generator()) or [[]]


class VariableClAutopartitionDeviceDev(FieldValue, ReadonlyVariable):
    type = "list"
    source_variable = "cl_autopartition_device_data"
    column = 0


class VariableClAutopartitionDeviceType(FieldValue, ReadonlyVariable):
    type = "list"
    source_variable = "cl_autopartition_device_data"
    column = 1


class VariableClAutopartitionDeviceSize(FieldValue, ReadonlyVariable):
    type = "list"
    source_variable = "cl_autopartition_device_data"
    column = 2


class VariableClAutopartitionDeviceName(FieldValue, ReadonlyVariable):
    type = "list"
    source_variable = "cl_autopartition_device_data"
    column = 3


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())
        devicesTypes = self.Select(['os_device_dev','os_device_type'],
            where='os_device_dev', _in=choiceVal)
        notFlashDevices = [x[0] for x in devicesTypes if x[1] != 'flash']
        if len(notFlashDevices) == 1:
            return [notFlashDevices[0]]
        return []

    def choice(self):
        getname = lambda dev, name, size: "%s (%s, %s)" % (
            dev, name, humanreadableSize(size)
        )
        return [(dev, getname(dev, name, size))
                for dev, name, size in self.ZipVars(
                'cl_autopartition_device_dev', 'cl_autopartition_device_name',
                'cl_autopartition_device_size')]

    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 checkOnSelectParentAndChild(self, valuelist):
        """
        Проверка на попытку одновременно выбрать RAID и диски из которых
        он состоит
        """
        for device in valuelist:
            parents = self.select('os_device_parent',
                                  os_device_dev=device, limit=1).split(',')
            common = set(parents) & set(valuelist)
            if common:
                if len(common) > 1:
                    raise VariableError(
                        _("Devices {devices} are used for {selected}").format(
                            devices=",".join(sorted(common)),
                            selected=device))
                else:
                    raise VariableError(
                        _("Device {device} is used for {selected}").format(
                            device=",".join(common),
                            selected=device))

    def checkSelectedRaid(self, valuelist):
        """
        Проверить схемы RAID, чтобы исключить базирование их на lvm
        """
        typecheck = re.compile("lvm.*raid")
        for dev, fulltype in self.ZipVars("os_device_dev",
                                          "os_device_fulltype"):
            if dev in valuelist:
                if typecheck.search(fulltype):
                    raise VariableError(_("RAID %s is wrong") % dev)

    def is_force_param(self):
        return "--force" in self.Get("cl_console_args")

    def get_mounted_devices(self):
        mountedData = [(x,y) for x,y in self.ZipVars(
            "os_disk_parent", "os_disk_dev") if y and isMount(y)]
        mountedDevices = {}
        for devices, disk  in mountedData:
            if not isMount(disk):
                continue
            if self.is_force_param() and try_umount(disk):
                continue
            for _device in traverse(devices.split(',')):
                if _device not in mountedDevices:
                    mountedDevices[_device] = set()
                mountedDevices[_device].add(disk)
        return mountedDevices

    def check(self, valuelist):
        if self.Get('cl_autopartition_set') == "on":
            if not valuelist:
                raise VariableError(
                    _("For autopartition, please select the device"))

            useDisks = set(traverse(x.split(',') for x in self.Select(
                    'os_disk_parent', where='os_disk_mount', ne='')))

            mountedDevices = self.get_mounted_devices()

            for value in chain(valuelist):
                for disk in set(chain(self.select(
                        'os_device_parent', os_device_dev=value,
                        limit=1).split(","), [value])):
                    if disk in useDisks:
                        raise VariableError(
                            _("Device %s is already in use by the current "
                              "system") % value)
                    if disk in mountedDevices:
                        disks = ",".join(sorted(mountedDevices[disk]))
                        raise VariableError(
                            _("Please unmount {disks}, as {device} will be used for "
                              "installation").format(device=disk, disks=disks))
            self.checkSelectedRaid(valuelist)
            self.checkOnSelectParentAndChild(valuelist)
            self.checkOnLvm(valuelist)
            self.checkOnRaid(valuelist)
            try:
                df = partition.VariableFactory(device.udev)
                sb = self.Get('cl_autopartition_scheme_builder')
                if sb and sb.devices:
                    sb.process(df)
            except (partition.SchemeError,
                    partition.VirtualDiskError) as e:
                raise VariableError(str(e))
            if self.GetBool('cl_autopartition_uefi_set'):
                if not self.Get('cl_autopartition_efi') and \
                    self.Get('cl_autopartition_table') == 'gpt':
                    raise VariableError(
                        _("Selected devices have not EFI partition"))
            if not self.GetBool('cl_autopartition_uefi_set'):
                if not self.Get('cl_autopartition_mbr'):
                    raise VariableError(
                        _("Selected devices have not BIOS boot partition"))


class VariableClAutopartitionSchemeBuilder(ReadonlyVariable):
    def get(self):
        if not self.GetBool('cl_autopartition_set'):
            return ""
        sb = partition.SchemeBuilder()
        if self.Get('cl_autopartition_table') == "dos":
            sb.partition_table = sb.PartitionTable.DOS
        if self.Get('cl_autopartition_table') == "gpt":
            sb.partition_table = sb.PartitionTable.GPT
        sb.vgname = self.Get('cl_autopartition_lvm_vgname')
        sb.lvm = self.GetBool('cl_autopartition_lvm_set')
        sb.efi = self.GetBool('cl_autopartition_uefi_set')

        sb.efi_size = int(self.Get('cl_autopartition_uefi_size'))
        sb.biosboot_size = int(self.Get('cl_autopartition_bios_grub_size'))
        sb.root_size = int(self.Get('cl_autopartition_root_size'))
        sb.swap_size = int(self.Get('cl_autopartition_swap_size'))

        scheme = self.Get('cl_autopartition_scheme')
        if SchemeOpt.Swap in scheme:
            sb.swap = True
        if SchemeOpt.Update in scheme:
            sb.update = True
        if SchemeOpt.Calculate in scheme:
            sb.calculate = True

        deviceSizeAll = int(self.Get('cl_autopartition_device_size_all'))
        if not sb.update and not sb.calculate and deviceSizeAll == sb.root_size:
            sb.rootall = True

        sb.minimal_calculate_size = \
            int(self.Get('cl_autopartition_calculate_size'))

        used_devices = self.Get('cl_autopartition_device')
        for dev, _type, size in self.ZipVars("cl_autopartition_device_dev",
                                             "cl_autopartition_device_type",
                                             "cl_autopartition_device_size"):
            if dev in used_devices:
                if _type.startswith("raid"):
                    device_type = partition.VolumesBuilder.DeviceType.RAID
                else:
                    device_type = partition.VolumesBuilder.DeviceType.Device
                sb.add_device(dev, device_type, int(size))

        sb.default_format = self.Get('cl_autopartition_default_format')

        return sb

class VariableClAutopartitionFactory(ReadonlyVariable):
    """
    Объект для вычисления параметров/переменных выбранной конфигурации
    """

    def get(self):
        df = partition.VariableFactory(device.udev)
        sb = self.Get('cl_autopartition_scheme_builder')
        if sb and sb.devices:
            df.default_format = sb.default_format
            root_purpose = partition.VolumesBuilder.Purpose.MainRoot
            data_purpose = partition.VolumesBuilder.Purpose.Calculate
            df.purpose_format[root_purpose] = \
                self.Get('cl_autopartition_root_format')
            df.purpose_format[data_purpose] = \
                self.Get('cl_autopartition_calculate_format')
            try:
                sb.process(df)
            except (partition.SchemeError, partition.VirtualDiskError):
                return partition.VariableFactory(device.udev)
        return df

    def __str__(self):
        return "AutopartitionObject"


class VariableClAutopartitionDefaultFormat(Variable):
    """
    Формат файловой системы
    """
    type = "choice"

    def get(self):
        return FileSystemManager.get_default_fs(self, 'hdd')

    def choice(self):
        allfs = set([k for k,v in FileSystemManager.supportFS.items()
                     if v.get('auto',True)])
        availFS = set(self.Select('os_format_type',
                                  where='os_format_use',
                                  eq='yes'))
        return list(sorted(allfs & availFS))


class VariableClAutopartitionRootFormat(VariableClAutopartitionDefaultFormat):
    """
    Формат файловой системы для корня
    """
    opt = ["--root-fs"]
    metavalue = "FS"

    def init(self):
        self.label = _("Filesystem for root partition")
        self.help = _("set filesystem for root partititon")

    def get(self):
        return self.Get('cl_autopartition_default_format')


class VariableClAutopartitionCalculateFormat(VariableClAutopartitionDefaultFormat):
    """
    Формат файловой системы для calculate
    """
    opt = ["--data-fs"]
    metavalue = "FS"

    def init(self):
        self.label = _("Filesystem for data partition")
        self.help = _("set filesystem for data partititon")

    def get(self):
        return self.Get('cl_autopartition_default_format')


class VariableClAutopartitionCalculateSize(Variable):
    """
    Минимальный размер для раздела /var/calculate
    """
    value = str(Sizes.G * 1)


class VariableClAutopartitionSet(Variable):
    """
    Using autopartition
    """
    type = "bool"
    element = "radio"

    def init(self):
        self.label = _("Allocate drive space")
        self.help = _("use the autopartition")

    def choice(self):
        name = "Calculate Linux"
        return [("on", _("Erase disk and install %s") % name),
                ("off", _("Use current partitions"))]

    def get(self):
        if self.is_console_set("os_location_data"):
            return "off"
        elif self.is_console_set("cl_autopartition_device"):
            return "on"
        if self.Get('os_root_type_ext') in (RootType.Value.LiveCD,
                                            RootType.Value.LiveFlash,
                                            RootType.Value.IsoScanFlash,
                                            RootType.Value.NetBoot):
            return "on"
        else:
            return "off"


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, Variable):
    """
    Autopartition scheme
    """
    type = "choice-list"
    element = "selecttable"
    opt = ["--auto-scheme", "-S"]
    metavalue = "AUTOPARTOPTS"
    check_after = ["cl_autopartition_table"]
    untrusted = True

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

    def get(self):
        default_value = [
            SchemeOpt.Update,
            SchemeOpt.Calculate
        ]
        if self.Get('os_uefi_set') == 'on':
            return [SchemeOpt.UEFI] + default_value
        else:
            return default_value

    def choice(self):
        return [
            (SchemeOpt.Swap, _("Swap partition")),
            (SchemeOpt.Update, _("The partition for the update")),
            (SchemeOpt.Calculate, _("Data 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 VariableClAutopartitionDeviceSizeAll(ReadonlyVariable):
    """
    Общий объем дискового пространства
    """
    def get(self):
        deviceSize = sum(
            int(x) for x in self.Get('cl_autopartition_device_size'))
        scheme = self.Get('cl_autopartition_scheme')
        if SchemeOpt.Swap in scheme:
            return deviceSize - int(self.Get('cl_autopartition_swap_size'))
        return deviceSize

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 use_rootall(self):
        scheme = self.Get('cl_autopartition_scheme')
        return SchemeOpt.Update not in scheme and SchemeOpt.Calculate not in scheme

    def get(self):
        size = int(self.Get('cl_autopartition_root_size_default'))
        deviceSize = int(self.Get('cl_autopartition_device_size_all'))
        minRootSize = int(self.Get('cl_autopartition_root_size_min'))
        if self.use_rootall():
            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 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 VariableClAutopartitionDiskData(ReadonlyTableVariable):
    """
    Серия переменных содержит список устройств, используемых для построения
    переменных os_location_source и т.д.
    """
    source = ['cl_autopartition_disk_dev',
              'cl_autopartition_disk_mount',
              'cl_autopartition_disk_format',
              'cl_autopartition_disk_size',
              'cl_autopartition_disk_part',
              'cl_autopartition_disk_type']


class DiskFilter(VariableInterface):
    field = ""

    def get(self):
        factory = self.Get('cl_autopartition_factory')
        return [str(x) for x, mount in zip(getattr(factory, self.field),
                                           factory.disk_mount)
                if mount and not mount.startswith("/boot/efi")]

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


class VariableClAutopartitionDiskMount(DiskFilter, ReadonlyVariable):
    """
    Autopartition mount points
    """
    type = "list"
    field = "disk_mount"


class VariableClAutopartitionDiskFormat(DiskFilter, ReadonlyVariable):
    """
    Autopartition disk filesystem
    """
    type = "list"
    field = "disk_format"


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


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


class VariableClAutopartitionDiskSize(DiskFilter, ReadonlyVariable):
    """
    Autopartition disk size
    """
    type = "list"
    field = "disk_size"

    def get(self):
        return map(str, super(VariableClAutopartitionDiskSize, self).get())

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


class VariableClAutopartitionDiskDataFull(ReadonlyTableVariable):
    """
    Серия переменных содержит полный список созданной разметки
    """
    source = ['cl_autopartition_disk_dev_full',
              'cl_autopartition_disk_mount_full',
              'cl_autopartition_disk_format_full',
              'cl_autopartition_disk_perform_format_full',
              'cl_autopartition_disk_size_full',
              'cl_autopartition_disk_part_full',
              'cl_autopartition_disk_type_full']


class VariableClAutopartitionDiskDevFull(ReadonlyVariable):
    """
    Autopartition virtual disk on device
    """
    type = "list"

    def get(self):
        var_factory = self.Get('cl_autopartition_factory')
        return var_factory.disk_dev


class VariableClAutopartitionDiskMountFull(ReadonlyVariable):
    """
    Autopartition mount points
    """
    type = "list"

    def get(self):
        var_factory = self.Get('cl_autopartition_factory')
        return var_factory.disk_mount


class VariableClAutopartitionDiskFormatFull(ReadonlyVariable):
    """
    Autopartition disk filesystem
    """
    type = "list"

    def get(self):
        var_factory = self.Get('cl_autopartition_factory')
        return var_factory.disk_format


class VariableClAutopartitionDiskPerformFormatFull(ReadonlyVariable):
    """
    Autopartition disk filesystem
    """
    type = "bool-list"

    def get(self):
        return ["on" if mp else "off"
                for mp in self.Get('cl_autopartition_disk_mount_full')]

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

    def get(self):
        var_factory = self.Get('cl_autopartition_factory')
        return var_factory.disk_part


class VariableClAutopartitionDiskTypeFull(ReadonlyVariable):
    """
    Autopartition partition scheme (simple - disk-partition)
    """
    type = "list"

    def get(self):
        var_factory = self.Get('cl_autopartition_factory')
        return var_factory.disk_type


class VariableClAutopartitionDiskSizeFull(ReadonlyVariable):
    """
    Autopartition disk size
    """
    type = "list"

    def get(self):
        var_factory = self.Get('cl_autopartition_factory')
        return map(str, var_factory.disk_size)

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


class VariableClAutopartitionRaid(ReadonlyVariable):
    """
    Список выбранных для разметки RAID устройств
    """
    type = "list"

    def get(self):
        selected_dev = self.Get('cl_autopartition_device')
        return [
            dev for dev, _type in self.ZipVars(
            "cl_autopartition_device_dev", "cl_autopartition_device_type")
            if dev in selected_dev and _type.startswith("raid")]

class VariableClAutopartitionRaidParent(ReadonlyVariable):
    """
    Список физических устройств, из которых построены выбранные RAID
    """
    type = "list"

    def get(self):
        selected_raid = set(self.Get('cl_autopartition_raid'))
        return list(set(traverse(
            parents.split(',')
            for parents in self.select('os_device_parent',
                                       os_device_dev__in=selected_raid))))

class VariableClAutopartitionMbr(ReadonlyVariable):
    """
    Диски на которые будет установлен загрузчик
    """
    type = "list"

    def get(self):
        if self.GetBool('cl_autopartition_uefi_set'):
            return []

        selected_raid_devices = self.Get('cl_autopartition_raid_parent')

        mbr = (mbr for mbr in self.select(
            'os_device_mbr', os_device_dev__in=selected_raid_devices)
               if mbr)

        var_factory = self.Get('cl_autopartition_factory')
        return sorted(set(mbr) | set(var_factory.mbr))


class VariableClAutopartitionEfi(ReadonlyVariable):
    """
    Диски на которые будет установлен загрузчик
    """
    type = "list"

    def get(self):
        if not self.GetBool('cl_autopartition_uefi_set'):
            return []

        var_factory = self.Get('cl_autopartition_factory')
        selected_dev = self.Get('cl_autopartition_device')
        selected_data = ((dev, _type) for dev, _type in self.ZipVars(
            "cl_autopartition_device_dev", "cl_autopartition_device_type")
            if dev in selected_dev)
        for dev, _type in selected_data:
            if _type == "device":
                if var_factory.efi:
                    return var_factory.efi[:1]
            elif _type == "raid0":
                parents = self.select('os_device_parent',
                                      os_device_dev=dev, limit=1).split(',')
                efidev = self.select('os_device_efi', os_device_dev__in=parents,
                                     limit=1)
                if efidev:
                    return [efidev]
            # прочие raid
            else:
                parents = self.select('os_device_parent',
                                      os_device_dev=dev, limit=1).split(',')
                efidevs = self.select('os_device_efi',
                                      os_device_dev__in=parents)
                if efidevs:
                    return sorted(set(filter(None, efidevs)))
        return []


class VariableClAutopartitionParent(ReadonlyVariable):
    """
    disk_parent для install переменных
    """
    def get(self):
        def generate():
            for device in self.Get('cl_autopartition_raid_parent'):
                yield device
            selected_dev = self.Get('cl_autopartition_device')
            for dev, _type in self.ZipVars(
                    "cl_autopartition_device_dev",
                    "cl_autopartition_device_type"):
                if dev in selected_dev and not _type.startswith("raid"):
                    yield dev

        return ",".join(sorted(set(generate())))

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(50 * Sizes.M)


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

    def get(self, hr=HumanReadable.No):
        def generator():
            scheme = self.Get('cl_autopartition_scheme')
            if (self.Get('cl_autopartition_set') == "on" and
                        SchemeOpt.Calculate in scheme):
                yield ["/var/calculate/home", "/home"]
        return list(generator()) or [[]]


class VariableClAutopartitionBindPath(FieldValue, ReadonlyVariable):
    """
    Autopartition bind points
    """
    type = "list"
    source_variable = "cl_autopartition_bind_data"
    column = 0

    def get(self):
        return list(super(VariableClAutopartitionBindPath, self).get())

class VariableClAutopartitionBindMountpoint(FieldValue, ReadonlyVariable):
    """
    Autopartition bind points
    """
    type = "list"
    source_variable = "cl_autopartition_bind_data"
    column = 1

    def get(self):
        return list(super(VariableClAutopartitionBindMountpoint, self).get())
