"""Disk partition management module for GhostBSD Install Station.

Provides disk partitioning functionality including detection, creation, deletion
of partitions for GPT/MBR schemes with ZFS support and manages the partition database.
"""
import re
from time import sleep
from subprocess import Popen, PIPE, STDOUT, call
from install_station.data import query, zfs_datasets, InstallationData

# Define required file paths


def get_disk_from_partition(part: str) -> str:
    """Extract the disk name from a partition identifier.
    
    Args:
        part (str): Partition identifier (e.g., 'ada0p1', 'ada0s1a')
        
    Returns:
        str: Disk name (e.g., 'ada0')
    """
    if set("p") & set(part):
        return part.partition('p')[0]
    else:
        return part.partition('s')[0]


def slice_number(part: str) -> int:
    """Extract the slice/partition number from a partition identifier.
    
    Args:
        part (str): Partition identifier (e.g., 'ada0p1', 'ada0s1')
        
    Returns:
        int: Slice/partition number
    """
    if set("p") & set(part):
        return int(part.partition('p')[2])
    else:
        return int(part.partition('s')[2])


def find_next_partition(partition_name: str, partition_list: list[str]) -> str:
    """Find the next available partition name with sequential numbering.
    
    Args:
        partition_name (str): Base partition name (e.g., 'freespace', 'ada0p')
        partition_list (list): List of existing partition names
        
    Returns:
        str: Next available partition name (e.g., 'freespace1', 'ada0p2')
    """
    for num in range(1, 10000):
        if f'{partition_name}{num}' not in partition_list:
            return f'{partition_name}{num}'


def disk_list() -> list[str]:
    """Get a list of available disk devices on the system.
    
    Queries the FreeBSD kernel for available disks and filters out
    optical drives (CD/DVD devices).
    
    Returns:
        list: Sorted list of disk device names (e.g., ['ada0', 'ada1'])
    """
    disk_popen = Popen(
        'sysctl -n kern.disks',
        shell=True,
        stdin=PIPE,
        stdout=PIPE,
        universal_newlines=True,
        close_fds=True
    )
    disks = disk_popen.stdout.read()
    cleaned_disk = re.sub(r'acd[0-9]*|cd[0-9]*|scd[0-9]*', '', disks)
    return sorted(cleaned_disk.split())


def device_model(disk: str) -> str:
    """Get the model description of a disk device.
    
    Args:
        disk (str): Disk device name (e.g., 'ada0')
        
    Returns:
        str: Device model description
    """
    device_popen = Popen(
        f"diskinfo -v {disk} 2>/dev/null | grep 'Disk descr' | cut -d '#' -f1 | tr -d '\t'",
        shell=True,
        stdin=PIPE,
        stdout=PIPE,
        universal_newlines=True,
        close_fds=True
    )
    return device_popen.stdout.read().strip()


def disk_size(disk: str) -> str:
    """Get the size of a disk device.
    
    Args:
        disk (str): Disk device name (e.g., 'ada0')
        
    Returns:
        str: Disk size information
    """
    disk_size_output = Popen(
        f"{query}/disk-info.sh {disk}",
        shell=True,
        stdin=PIPE,
        stdout=PIPE,
        universal_newlines=True,
        stderr=STDOUT,
        close_fds=True
    )
    return disk_size_output.stdout.readlines()[0].rstrip()


def get_scheme(disk: str) -> str:
    """Detect the partition scheme of a disk device.
    
    Args:
        disk (str): Disk device name (e.g., 'ada0')
        
    Returns:
        str: Partition scheme ('GPT', 'MBR', or empty if none)
    """
    scheme_output = Popen(
        f"{query}/detect-scheme.sh {disk}",
        shell=True,
        stdin=PIPE,
        stdout=PIPE,
        universal_newlines=True,
        stderr=STDOUT,
        close_fds=True
    )
    return scheme_output.stdout.readlines()[0].rstrip()


class DiskPartition:
    """Main class for disk partition detection and database management.
    
    This class provides methods to scan disk devices, detect existing partitions,
    and maintain an in-memory database of partition information for both GPT and
    MBR partition schemes.
    
    Attributes:
        disk_database (dict): In-memory database of disk and partition information
        query_partition (str): Path to disk partition query script
    """
    disk_database: dict = {}

    query_partition = f'{query}/disk-part.sh'

    @classmethod
    def mbr_partition_slice_db(cls, disk):
        """Create database of MBR slices and their partitions.
        
        Args:
            disk (str): Disk device name (e.g., 'ada0')
            
        Returns:
            dict: Database of slices with their partition information
        """
        partition_output = Popen(
            f'{cls.query_partition} {disk}',
            shell=True,
            stdin=PIPE,
            stdout=PIPE,
            universal_newlines=True
        )
        slice_db = {}
        free_num = 1
        for line in partition_output.stdout:
            info = line.strip().split()
            slice_name = info[0]
            if 'freespace' in line:
                slice_name = f'freespace{free_num}'
                free_num += 1
            part_db = cls.mbr_partition_db(info[0])
            part_list = [] if part_db is None else list(part_db.keys())
            partitions = {
                'name': slice_name,
                'size': info[1].partition('M')[0],
                'mount-point': '',
                'file-system': info[2],
                'stat': None,
                'partitions': part_db,
                'partition-list': part_list
            }
            slice_db[slice_name] = partitions
        return slice_db

    @classmethod
    def mbr_partition_db(cls, partition_slice):
        """Create database of partitions within an MBR slice.
        
        Args:
            partition_slice (str): Slice identifier (e.g., 'ada0s1')
            
        Returns:
            dict or None: Database of partitions within the slice, or None for freespace
        """
        if 'freespace' in partition_slice:
            return None
        else:
            slice_output = Popen(
                f'{query}/disk-label.sh {partition_slice}',
                shell=True,
                stdin=PIPE,
                stdout=PIPE,
                universal_newlines=True
            )
            partition_db = {}
            alph = ord('a')
            free_num = 1
            for line in slice_output.stdout:
                info = line.strip().split()
                if 'freespace' in line:
                    partition_name = f'freespace{free_num}'
                    free_num += 1
                else:
                    letter = chr(alph)
                    partition_name = f'{partition_slice}{letter}'
                    alph += 1
                partitions = {
                    'name': partition_name,
                    'size': info[0].partition('M')[0],
                    'mount-point': '',
                    'file-system': info[2],
                    'stat': None,
                }
                partition_db[partition_name] = partitions
            if not partition_db:
                return None
            return partition_db

    @classmethod
    def gpt_partition_db(cls, disk):
        """Create database of GPT partitions on a disk.
        
        Args:
            disk (str): Disk device name (e.g., 'ada0')
            
        Returns:
            dict: Database of GPT partitions
        """
        partition_output = Popen(
            f'{cls.query_partition} {disk}',
            shell=True,
            stdin=PIPE,
            stdout=PIPE,
            universal_newlines=True
        )
        partition_db = {}
        free_num = 1
        for line in partition_output.stdout:
            info = line.strip().split()
            slice_name = info[0]
            if 'freespace' in line:
                slice_name = f'freespace{free_num}'
                free_num += 1
            partitions = {
                'name': info[0],
                'size': info[1].partition('M')[0],
                'mount-point': '',
                'file-system': info[2],
                'stat': None,
                'partitions': {},
                'partition-list': []
            }
            partition_db[slice_name] = partitions
        return partition_db

    @classmethod
    def create_partition_database(cls):
        """Scan all disks and create comprehensive partition database.
        
        This method queries all available disks, detects their partition schemes,
        and builds a complete database of disk and partition information.
        """
        # if os.path.exists(disk_db_file):
        #     os.remove(disk_db_file)
        # drives_database = open(disk_db_file, 'wb')
        disk_db = {}
        for disk in disk_list():
            disk_info_db = {}
            disk_info_db.setdefault('scheme', get_scheme(disk))
            if disk_info_db['scheme'] == "GPT":
                part_db = cls.gpt_partition_db(disk)
            elif disk_info_db['scheme'] == "MBR":
                part_db = cls.mbr_partition_slice_db(disk)
            else:
                disk_info_db['scheme'] = None
                part_db = {}
            part_list = [] if part_db is None else list(part_db.keys())
            disk_info_db['size'] = disk_size(disk)
            disk_info_db['device_model'] = device_model(disk)
            disk_info_db['partitions'] = part_db
            disk_info_db['partition-list'] = part_list
            disk_info_db['stat'] = None
            disk_db[disk] = disk_info_db
        cls.disk_database = disk_db

    @classmethod
    def get_disk_database(cls):
        """Get the current disk database.
        
        Returns:
            dict: Current disk and partition database
        """
        return cls.disk_database.copy()

    @classmethod
    def how_partition(cls, disk):
        """Get the number of partitions on a disk.
        
        Args:
            disk (str): Disk device name
            
        Returns:
            int: Number of partitions on the disk
        """
        return len(cls.disk_database[disk]['partitions'])

    @classmethod
    def set_disk_scheme(cls, scheme, disk, size):
        """Set or update the partitioning scheme for a disk.
        
        Args:
            scheme (str or None): Partition scheme ('GPT' or 'MBR')
            disk (str): Disk device name
            size (str): Disk size
        """
        if scheme is None:
            cls.disk_database[disk]['scheme'] = 'GPT'
        else:
            cls.disk_database[disk]['scheme'] = scheme
        # this need to data and not use pickle with open.
        InstallationData.destroy[disk] = scheme
        if not cls.disk_database[disk]['partitions']:
            cls.disk_database[disk]['partitions'] = {
                'freespace1': {
                    'name': 'freespace1',
                    'size': size,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
            }
            cls.disk_database[disk]['partition-list'] = [
                'freespace1'
            ]


class DeletePartition:
    """Class for handling partition deletion operations.
    
    This class provides methods to delete partitions from both GPT and MBR
    partition schemes, handling the consolidation of free space and updating
    the partition database accordingly.
    """

    def find_if_label(self, part):
        """Check if a partition identifier represents a BSD label.
        
        Args:
            part (str): Partition identifier
            
        Returns:
            bool: True if the partition has a BSD label suffix (letter)
        """
        last = part[-1]
        if re.search('[a-z]', last):
            return True
        return False

    def delete_label(self, drive, label, partition, path):
        """Delete a BSD label partition and consolidate free space.
        
        Args:
            drive (str): Disk device name
            label (str): Label partition to delete
            partition (str): Parent slice containing the label
            path (list): Path information for partition location
        """
        disk_partitions = DiskPartition.disk_database[drive]['partitions'][partition]
        partitions_info = disk_partitions['partitions']
        label_list = disk_partitions['partition-list']
        last_list_number = len(label_list) - 1
        store_list_number = path[2]
        size_free = int(partitions_info[label]['size'])

        if last_list_number == store_list_number and len(label_list) > 1:
            label_behind = label_list[store_list_number - 1]
            if 'freespace' in label_behind:
                size_free += int(partitions_info[label_behind]['size'])
                label_list.remove(label)
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'][label_behind] = {
                    'name': label_behind,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
            else:
                free_name = find_next_partition('freespace', label_list)
                label_list[store_list_number] = free_name
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'][free_name] = {
                    'name': free_name,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
        elif store_list_number == 0 and len(label_list) > 1:
            label_after = label_list[store_list_number + 1]
            if 'freespace' in label_after:
                size_free += int(partitions_info[label_after]['size'])
                label_list.remove(label)
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'][label_after] = {
                    'name': label_after,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
            else:
                free_name = find_next_partition('freespace', label_list)
                label_list[store_list_number] = free_name
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'][free_name] = {
                    'name': free_name,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
        elif len(label_list) > 2:
            label_behind = label_list[store_list_number - 1]
            label_after = label_list[store_list_number + 1]
            size_behind = int(partitions_info[label_behind]['size'])
            size_after = int(partitions_info[label_after]['size'])
            if ('freespace' in label_behind
                    and 'freespace' in label_after):
                size_free += size_behind + size_after
                label_list.remove(label)
                label_list.remove(label_after)
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'].pop(label_after, None)
                disk_partitions['partitions'][label_behind] = {
                    'name': label_behind,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
            elif 'freespace' in label_behind:
                size_free += size_behind
                label_list.remove(label)
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'][label_behind] = {
                    'name': label_behind,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
            elif 'freespace' in label_after:
                size_free += size_after
                label_list.remove(label)
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'].pop(label_after, None)
                disk_partitions['partitions'][label_after] = {
                    'name': label_after,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
            else:
                free_name = find_next_partition('freespace', label_list)
                label_list[store_list_number] = free_name
                disk_partitions['partitions'].pop(label, None)
                disk_partitions['partitions'][free_name] = {
                    'name': free_name,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_partitions['partition-list'] = label_list
        else:
            free_name = find_next_partition('freespace', label_list)
            label_list[store_list_number] = free_name
            disk_partitions['partitions'].pop(label, None)
            disk_partitions['partitions'][free_name] = {
                'name': free_name,
                'size': size_free,
                'mount-point': '',
                'file-system': 'none',
                'stat': None,
                'partitions': {},
                'partition-list': []
            }
            disk_partitions['partition-list'] = label_list

        # Data is already updated in DiskPartition.disk_database - no need to save to file
        remaining_partition = []
        for part in label_list:
            partitions_info = disk_partitions['partitions']
            if part in partitions_info:
                size = partitions_info[part]['size']
                mount_point = partitions_info[part]['mount-point']
                file_system = partitions_info[part]['file-system']
                stat = partitions_info[part]['stat']
                if stat == 'New':
                    remaining_partition.append(f'{file_system} {size} {mount_point}\n')
        InstallationData.new_partition = remaining_partition

    def __init__(self, part, path):
        """Initialize partition deletion operation.
        
        Args:
            part (str): Partition identifier to delete
            path (list): Path information for partition location
        """
        drive = get_disk_from_partition(part)
        if part == "freespace":
            pass
        elif self.find_if_label(part) is True:
            spart = part[:-1]
            self.delete_label(drive, part, spart, path)
        else:
            self.delete_slice(drive, part, path)

    def delete_slice(self, drive, partition, path):
        """Delete a slice/partition and consolidate adjacent free space.
        
        Args:
            drive (str): Disk device name
            partition (str): Partition to delete
            path (list): Path information for partition location
        """
        disk_data = DiskPartition.disk_database
        partitions_info = disk_data[drive]['partitions']
        partition_list = disk_data[drive]['partition-list']
        last_list_number = len(partition_list) - 1
        store_list_number = path[1]
        size_free = int(partitions_info[partition]['size'])
        if last_list_number == store_list_number and len(partition_list) > 1:
            partition_behind = partition_list[store_list_number - 1]
            if 'freespace' in partition_behind:
                size_free += int(partitions_info[partition_behind]['size'])
                partition_list.remove(partition)
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][partition_behind] = {
                    'name': partition_behind,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
            else:
                free_name = find_next_partition('freespace', partition_list)
                partition_list[store_list_number] = free_name
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][free_name] = {
                    'name': free_name,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
        elif store_list_number == 0 and len(partition_list) > 1:
            partition_after = partition_list[store_list_number + 1]
            if 'freespace' in partition_after:
                size_free += int(partitions_info[partition_after]['size'])
                partition_list.remove(partition)
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][partition_after] = {
                    'name': partition_after,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
            else:
                free_name = find_next_partition('freespace', partition_list)
                partition_list[store_list_number] = free_name
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][free_name] = {
                    'name': free_name,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
        elif len(partition_list) > 2:
            partition_behind = partition_list[store_list_number - 1]
            partition_after = partition_list[store_list_number + 1]
            size_behind = int(partitions_info[partition_behind]['size'])
            size_after = int(partitions_info[partition_after]['size'])
            if ('freespace' in partition_behind
                    and 'freespace' in partition_after):
                size_free += size_behind + size_after
                partition_list.remove(partition)
                partition_list.remove(partition_after)
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][partition_behind] = {
                    'name': partition_behind,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
            elif 'freespace' in partition_behind:
                size_free += size_behind
                partition_list.remove(partition)
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][partition_behind] = {
                    'name': partition_behind,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
            elif 'freespace' in partition_after:
                size_free += size_after
                partition_list.remove(partition)
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][partition_after] = {
                    'name': partition_after,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
            else:
                free_name = find_next_partition('freespace', partition_list)
                partition_list[store_list_number] = free_name
                disk_data[drive]['partitions'].pop(partition, None)
                disk_data[drive]['partitions'][free_name] = {
                    'name': free_name,
                    'size': size_free,
                    'mount-point': '',
                    'file-system': 'none',
                    'stat': None,
                    'partitions': {},
                    'partition-list': []
                }
                disk_data[drive]['partition-list'] = partition_list
        else:
            free_name = find_next_partition('freespace', partition_list)
            partition_list[store_list_number] = free_name
            disk_data[drive]['partitions'].pop(partition, None)
            disk_data[drive]['partitions'][free_name] = {
                'name': free_name,
                'size': size_free,
                'mount-point': '',
                'file-system': 'none',
                'stat': None,
                'partitions': {},
                'partition-list': []
            }
            disk_data[drive]['partition-list'] = partition_list

        # if delete file exist check if slice is in the list

        if partition not in InstallationData.delete:
            InstallationData.delete.append(partition)

        if "p" in partition and InstallationData.new_partition:
            remaining_partition = []
            for part in partition_list:
                partitions_info = disk_data[drive]['partitions']
                size = partitions_info[part]['size']
                mount_point = partitions_info[part]['mount-point']
                file_system = partitions_info[part]['file-system']
                stat = partitions_info[part]['stat']
                if stat == 'New':
                    remaining_partition.append(f'{file_system} {size} {mount_point}\n')
            InstallationData.new_partition = remaining_partition


class AutoFreeSpace:
    """Class for automatic partition creation in free space.
    
    This class handles automatic partitioning of free space on disks,
    creating appropriate partition layouts for both MBR and GPT schemes
    with support for different filesystems.
    """

    def create_mbr_partiton(self, drive, size, path, fs):
        """Create MBR partitions automatically in free space.
        
        Creates a BSD slice with root and swap partitions.
        
        Args:
            drive (str): Disk device name
            size (str): Available size in MB
            path (list): Path information for partition location
            fs (str): Filesystem type ('ZFS' or 'UFS')
        """
        # remove 1M to size to avoid no space left
        main_size = int(size) - 1
        InstallationData.disk = drive

        InstallationData.scheme = 'partscheme=MBR'

        disk_db = DiskPartition.disk_database
        slice_list = disk_db[drive]['partition-list']
        store_list_number = path[1]
        main_slice = find_next_partition(f'{drive}s', slice_list)
        slice_list[store_list_number] = main_slice
        disk_db[drive]['partitions'][main_slice] = {
            'name': main_slice,
            'size': size,
            'mount-point': 'none',
            'file-system': 'BSD',
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }
        disk_db[drive]['partition-list'] = slice_list

        InstallationData.slice = main_slice.replace(drive, "")

        root_size = int(main_size)
        swap_size = 2048
        root_size -= swap_size

        part_list = disk_db[drive]['partitions'][main_slice]['partition-list']

        if fs == "ZFS":
            layout = zfs_datasets
        else:
            layout = '/'

        root_part = f'{main_slice}a'
        part_list.append(root_part)
        disk_db[drive]['partitions'][main_slice]['partitions'][root_part] = {
            'name': root_part,
            'size': root_size,
            'mount-point': layout,
            'file-system': fs,
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }

        swap_part = f'{main_slice}b'
        part_list.append(swap_part)
        disk_db[drive]['partitions'][main_slice]['partitions'][swap_part] = {
            'name': swap_part,
            'size': swap_size,
            'mount-point': 'none',
            'file-system': 'SWAP',
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }

        disk_db[drive]['partitions'][main_slice]['partition-list'] = part_list

        # Data is already updated in DiskPartition.disk_database - no need to save to file
        
        # Add new partitions to InstallationData
        InstallationData.new_partition = [
            f'{fs} {root_size} {layout}\n',
            f'SWAP {swap_size} none\n'
        ]
        
        # Add to create list for partition creation operations
        InstallationData.create.append([main_slice, main_size])

    def __init__(self, path, size, fs, efi_exist, disk, scheme):
        """Initialize automatic partition creation.
        
        Args:
            path (list): Path information for partition location
            size (str): Available size in MB
            fs (str): Filesystem type ('ZFS' or 'UFS')
            efi_exist (bool): Whether EFI partition already exists
            disk (str): Disk device name
            scheme (str): Partition scheme ('GPT' or 'MBR')
        """
        self.bios_type = bios_or_uefi()
        if scheme == "GPT":
            self.create_gpt_partiton(disk, size, path, fs, efi_exist)
        elif scheme == "MBR":
            self.create_mbr_partiton(disk, size, path, fs)

    def create_gpt_partiton(self, drive, size, path, fs, efi_exist):
        """Create GPT partitions automatically in free space.
        
        Creates boot (if needed), root, and swap partitions with appropriate
        sizing for the target filesystem.
        
        Args:
            drive (str): Disk device name
            size (str): Available size in MB
            path (list): Path information for partition location
            fs (str): Filesystem type ('ZFS' or 'UFS')
            efi_exist (bool): Whether EFI partition already exists
        """
        # remove 1M to size to avoid no space left
        main_size = int(size) - 1
        InstallationData.disk = drive
        InstallationData.scheme = 'partscheme=GPT'
        root_size = int(main_size)
        swap_size = 2048
        root_size -= int(swap_size)
        if self.bios_type == "UEFI" and efi_exist is False:
            boot_size = 256
        else:
            boot_size = 1 if self.bios_type == "BIOS" else 0
        boot_name = 'UEFI' if self.bios_type == "UEFI" else 'BOOT'
        root_size -= boot_size
        disk_data = DiskPartition.disk_database
        partition_list = disk_data[drive]['partition-list']
        store_list_number = path[1]
        if boot_size != 0:
            boot_partition = find_next_partition(f'{drive}p', partition_list)
            partition_list[store_list_number] = boot_partition
            store_list_number += 1
            disk_data[drive]['partitions'][boot_partition] = {
                'name': boot_partition,
                'size': boot_size,
                'mount-point': 'none',
                'file-system': boot_name,
                'stat': 'New',
                'partitions': {},
                'partition-list': []
            }
            # Add boot partition to create list
            InstallationData.create.append([boot_partition, boot_size])

        if fs == "ZFS":
            layout = zfs_datasets
        else:
            layout = '/'

        root_partition = find_next_partition(f'{drive}p', partition_list)
        if store_list_number == path[1]:
            partition_list[store_list_number] = root_partition
        else:
            partition_list.insert(store_list_number, root_partition)
        store_list_number += 1
        disk_data[drive]['partitions'][root_partition] = {
            'name': root_partition,
            'size': root_size,
            'mount-point': layout,
            'file-system': fs,
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }

        InstallationData.slice = root_partition.replace(drive, '')

        swap_partition = find_next_partition(f'{drive}p', partition_list)
        partition_list.insert(store_list_number, swap_partition)
        disk_data[drive]['partitions'][swap_partition] = {
            'name': swap_partition,
            'size': swap_size,
            'mount-point': 'none',
            'file-system': 'SWAP',
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }

        disk_data[drive]['partition-list'] = partition_list
        
        # Data is already updated in DiskPartition.disk_database - no need to save to file
        
        # Add new partitions to InstallationData
        new_partitions = []
        if self.bios_type == "UEFI" and efi_exist is False:
            new_partitions.append(f'UEFI {boot_size} none\n')
        elif self.bios_type == "BIOS":
            new_partitions.append(f'BOOT {boot_size} none\n')
        new_partitions.extend([
            f'{fs} {root_size} {layout}\n',
            f'SWAP {swap_size} none\n'
        ])
        InstallationData.new_partition = new_partitions


class CreateLabel:
    """Class for creating BSD label partitions within MBR slices.
    
    This class handles the creation of individual partitions (labels)
    within BSD slices in MBR partition schemes.
    """
    def __init__(self, path, drive, main_slice, size_left, create_size,
                 mountpoint, fs):
        """Create a new BSD label partition within a slice.
        
        Args:
            path (list): Path information for partition location
            drive (str): Disk device name
            main_slice (str): Parent slice identifier
            size_left (int): Remaining size after partition creation
            create_size (int): Size of new partition in MB
            mountpoint (str): Mount point for the partition
            fs (str): Filesystem type
        """
        InstallationData.disk = drive
        InstallationData.scheme = 'partscheme=MBR'
        InstallationData.slice = main_slice.replace(drive, "")
        disk_db = DiskPartition.disk_database
        store_list_number = path[2]
        part_list = disk_db[drive]['partitions'][main_slice]['partition-list']
        alpha_num = ord('a')
        alpha_num += store_list_number
        letter = chr(alpha_num)
        if fs == "ZFS":
            mountpoint = zfs_datasets

        partition = f'{main_slice}{letter}'
        part_list[store_list_number] = partition
        disk_db[drive]['partitions'][main_slice]['partitions'][partition] = {
            'name': partition,
            'size': create_size,
            'mount-point': mountpoint,
            'file-system': fs,
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }
        if size_left != 0:
            free = find_next_partition('freespace', part_list)
            part_list.append(free)
            disk_db[drive]['partitions'][main_slice]['partitions'][free] = {
                'name': free,
                'size': size_left,
                'mount-point': '',
                'file-system': 'none',
                'stat': None,
                'partitions': {},
                'partition-list': []
            }

        disk_db[drive]['partitions'][main_slice]['partition-list'] = part_list

        # Data is already updated in DiskPartition.disk_database - no need to save to file
        
        # Update InstallationData with new partition information
        new_partitions = []
        for partition_name in part_list:
            drive_part = disk_db[drive]['partitions']
            part_info = drive_part[main_slice]['partitions'][partition_name]
            if part_info['stat'] == 'New':
                partition_text = f'{part_info["file-system"]} ' \
                    f'{part_info["size"]} ' \
                    f'{part_info["mount-point"]}\n'
                new_partitions.append(partition_text)
        
        InstallationData.new_partition = new_partitions


# class modifyLabel():

#     def __init_get_text(self, path, size_left, create_size, mount_point, fs,
#                  data, disk):
#         if not os.path.exists(disk_file):
#             file_disk = open(disk_file, 'w')
#             file_disk.writelines('%s\n' % disk)
#             file_disk.close()
#         sl = path[1] + 1
#         lv = path[2]
#         write_scheme = open(scheme_file, 'w')
#         write_scheme.writelines('partscheme=MBR')
#         write_scheme.close()
#         write_slice = open(slice_file, 'w')
#         write_slice.writelines('s%s\n' % sl)
#         write_slice.close()
#         alph = ord('a')
#         alph += lv
#         letter = chr(alph)
#         llist = []
#         mllist = label_query(disk + 's%s' % sl)
#         plf = open(partitiondb + disk + 's%s' % sl, 'wb')
#         if size_left == 0:
#             create_size -= 1
#         llist.extend(([disk + 's%s' % sl + letter, create_size, mount_point,
#                       fs]))
#         mllist[lv] = llist
#         llist = []
#         if size_left > 0:
#             llist.extend((['freespace', size_left, '', '']))
#             mllist.append(llist)
#         pickle.dump(mllist, plf)
#         plf.close()
#         llist = open(partitiondb + disk + 's%s' % sl, 'rb')
#         sabeltlist = pickle.load(llist)
#         write_partition = open(partition_label_file, 'w')
#         for partlist in sabeltlist:
#             if partlist[2] != '':
#                 write_partition.writelines('%s %s %s\n' % (partlist[3],
#                                            partlist[1], partlist[2]))
#         write_partition.close()


class CreateSlice:
    """Class for creating MBR slices (BSD partitions).
    
    This class handles the creation of BSD slices within MBR partition
    schemes, which can then contain multiple BSD label partitions.
    """

    def __init__(self, create_size, size_left, path, drive):
        """Create a new MBR slice.
        
        Args:
            create_size (int): Size of new slice in MB
            size_left (int): Remaining size after slice creation
            path (list): Path information for slice location
            drive (str): Disk device name
        """
        InstallationData.disk = drive
        InstallationData.scheme = 'partscheme=MBR'

        disk_db = DiskPartition.disk_database
        store_list_number = path[1]
        partition_list = disk_db[drive]['partition-list']

        partition = find_next_partition(f'{drive}s', partition_list)

        partition_list[store_list_number] = partition
        # Store slice partition
        disk_db[drive]['partitions'][partition] = {
            'name': partition,
            'size': create_size,
            'mount-point': 'none',
            'file-system': 'BSD',
            'stat': 'New',
            'partitions': {},
            'partition-list': ['freespace1']
        }
        # Store freespace for partition partition
        disk_db[drive]['partitions'][partition]['partitions']['freespace1'] = {
            'name': 'freespace1',
            'size': create_size,
            'mount-point': '',
            'file-system': 'none',
            'stat': None,
            'partitions': {},
            'partition-list': []
        }
        # Store freespace if some left
        if size_left != 0:
            free_name = find_next_partition('freespace', partition_list)
            partition_list.append(free_name)
            disk_db[drive]['partitions'][free_name] = {
                'name': free_name,
                'size': size_left,
                'mount-point': '',
                'file-system': 'none',
                'stat': None,
                'partitions': {},
                'partition-list': []
            }

        disk_db[drive]['partition-list'] = partition_list

        # Data is already updated in DiskPartition.disk_database - no need to save to file

        InstallationData.slice = partition.replace(drive, '')

        # Add to create list for partition creation operations
        InstallationData.create.append([partition, create_size])


class CreatePartition():
    """Class for creating GPT partitions.
    
    This class handles the creation of individual partitions within
    GPT partition schemes, supporting various filesystem types and
    mount points.
    """
    def __init__(self, path, drive, size_left, create_size, mount_point, fs):
        """Create a new GPT partition.
        
        Args:
            path (list): Path information for partition location
            drive (str): Disk device name
            size_left (int): Remaining size after partition creation
            create_size (int): Size of new partition in MB
            mount_point (str): Mount point for the partition
            fs (str): Filesystem type
        """
        InstallationData.disk = drive

        InstallationData.scheme = 'partscheme=GPT'

        if fs == "ZFS":
            mount_point = zfs_datasets

        disk_data = DiskPartition.disk_database
        store_list_number = path[1]
        partition_list = disk_data[drive]['partition-list']

        partition = find_next_partition(f'{drive}p', partition_list)

        partition_list[store_list_number] = partition
        # Store slice partition
        disk_data[drive]['partitions'][partition] = {
            'name': partition,
            'size': create_size,
            'mount-point': mount_point,
            'file-system': fs,
            'stat': 'New',
            'partitions': {},
            'partition-list': []
        }
        # Store freespace if some left
        if size_left != 0:
            free_name = find_next_partition('freespace', partition_list)
            partition_list.append(free_name)
            disk_data[drive]['partitions'][free_name] = {
                'name': free_name,
                'size': size_left,
                'mount-point': '',
                'file-system': 'none',
                'stat': None,
                'partitions': {},
                'partition-list': []
            }

        disk_data[drive]['partition-list'] = partition_list

        # Data is already updated in DiskPartition.disk_database - no need to save to file

        if mount_point == '/' or fs == "ZFS":
            InstallationData.slice = partition.replace(drive, '')

        if fs == "UEFI" or fs == "BOOT":
            # Add to create list for partition creation operations
            InstallationData.create.append([partition, create_size])

        # Update InstallationData with new partition information
        new_partitions = []
        for partition_name in partition_list:
            partition_info = disk_data[drive]['partitions'][partition_name]
            if partition_info['stat'] == 'New':
                partition_text = f'{partition_info["file-system"]} ' \
                    f'{partition_info["size"]} ' \
                    f'{partition_info["mount-point"]}\n'
                new_partitions.append(partition_text)

        InstallationData.new_partition = new_partitions


def delete_partition() -> None:
    """Execute physical deletion of partitions marked for deletion.
    
    Iterates through partitions marked for deletion in InstallationData
    and removes them from the disk using FreeBSD gpart commands.
    
    Raises:
        RuntimeError: If no partitions are marked for deletion
    """
    if InstallationData.delete:
        for partition in InstallationData.delete:
            num = slice_number(partition)
            drive = get_disk_from_partition(partition)
            call(f"sudo zpool labelclear -f {partition}", shell=True)
            sleep(1)
            call(f'sudo gpart delete -i {num} {drive}', shell=True)
            sleep(1)
    else:
        raise RuntimeError('No partitions to delete')


def destroy_partition() -> None:
    """Destroy and recreate partition tables on disks.
    
    Completely destroys existing partition tables and creates new ones
    with the specified scheme for disks marked for destruction.
    
    Raises:
        RuntimeError: If no disks are marked for destruction
    """
    if InstallationData.destroy:
        for drive, scheme in InstallationData.destroy.items():
            # Destroy the disk geom
            gpart_destroy = f"sudo gpart destroy -F {drive}"
            call(gpart_destroy, shell=True)
            sleep(1)
            clear_drive = f"sudo dd if=/dev/zero of={drive} bs=1m count=1"
            call(clear_drive, shell=True)
            sleep(1)
            call(f'sudo gpart create -s {scheme} {drive}', shell=True)
            sleep(1)
    else:
        raise RuntimeError('No disks to destroy')


def bios_or_uefi() -> str:
    """Detect the system boot method (BIOS or UEFI).
    
    Returns:
        str: 'BIOS' or 'UEFI' depending on the system boot method
    """
    cmd = "sysctl -n machdep.bootmethod"
    output1 = Popen(cmd, shell=True, stdout=PIPE,
                    universal_newlines=True, close_fds=True)
    return output1.stdout.readlines()[0].rstrip()


def add_partition() -> None:
    """Execute physical creation of partitions marked for creation.
    
    Creates actual partitions on disk using FreeBSD gpart commands
    for all partitions marked for creation in InstallationData.
    Handles different partition types (EFI, BIOS boot, FreeBSD, etc.).
    
    Raises:
        RuntimeError: If no partitions are marked for creation
    """
    if InstallationData.create:
        boot = InstallationData.boot
        for partition_info in InstallationData.create:
            part = partition_info[0]
            size = int(partition_info[1])
            drive = get_disk_from_partition(part)
            sl = slice_number(part)
            if set("p") & set(part):
                if bios_or_uefi() == 'UEFI':
                    cmd = f'sudo gpart add -a 4k -s {size}M -t efi ' \
                        f'-i {sl} {drive}'
                    call(cmd, shell=True)
                    sleep(1)
                    call(f'sudo zpool labelclear -f {drive}p{sl}', shell=True)
                    cmd2 = f'sudo newfs_msdos -F 16 {drive}p{sl}'
                    call(cmd2, shell=True)
                else:
                    if boot == "grub":
                        cmd = f'sudo gpart add -a 4k -s {size}M -t ' \
                            'bios-boot -i {sl} {drive}'
                    else:
                        # freebsd-boot partition must never be larger
                        # than 512B blocks.
                        cmd = 'sudo gpart add -a 4k -s 512 -t ' \
                            f'freebsd-boot -i {sl} {drive}'
                    call(cmd, shell=True)
                    call(f'sudo zpool labelclear -f {drive}p{sl}', shell=True)
            elif set("s") & set(part):
                cmd = f'sudo gpart add -a 4k -s {size}M -t freebsd ' \
                    f'-i {sl} {drive}'
                call(cmd, shell=True)
            sleep(2)
    else:
        raise RuntimeError('No partitions to create')
