import os
from abstract import AbstractPartition
from bootstrapvz.common.sectors import Sectors


class BasePartition(AbstractPartition):
    """Represents a partition that is actually a partition (and not a virtual one like 'Single')
    """

    # Override the states of the abstract partition
    # A real partition can be mapped and unmapped
    events = [{'name': 'create', 'src': 'nonexistent', 'dst': 'unmapped'},
              {'name': 'map', 'src': 'unmapped', 'dst': 'mapped'},
              {'name': 'format', 'src': 'mapped', 'dst': 'formatted'},
              {'name': 'mount', 'src': 'formatted', 'dst': 'mounted'},
              {'name': 'unmount', 'src': 'mounted', 'dst': 'formatted'},
              {'name': 'unmap', 'src': 'formatted', 'dst': 'unmapped_fmt'},

              {'name': 'map', 'src': 'unmapped_fmt', 'dst': 'formatted'},
              {'name': 'unmap', 'src': 'mapped', 'dst': 'unmapped'},
              ]

    def __init__(self, size, filesystem, format_command, mountopts, previous):
        """
        :param Bytes size: Size of the partition
        :param str filesystem: Filesystem the partition should be formatted with
        :param list format_command: Optional format command, valid variables are fs, device_path and size
        :param BasePartition previous: The partition that preceeds this one
        """
        # By saving the previous partition we have a linked list
        # that partitions can go backwards in to find the first partition.
        self.previous = previous
        # List of flags that parted should put on the partition
        self.flags = []
        # Path to symlink in /dev/disk/by-uuid (manually maintained by this class)
        self.disk_by_uuid_path = None
        super(BasePartition, self).__init__(size, filesystem, format_command, mountopts)

    def create(self, volume):
        """Creates the partition

        :param Volume volume: The volume to create the partition on
        """
        self.fsm.create(volume=volume)

    def get_index(self):
        """Gets the index of this partition in the partition map

        :return: The index of the partition in the partition map
        :rtype: int
        """
        if self.previous is None:
            # Partitions are 1 indexed
            return 1
        else:
            # Recursive call to the previous partition, walking up the chain...
            return self.previous.get_index() + 1

    def get_start(self):
        """Gets the starting byte of this partition

        :return: The starting byte of this partition
        :rtype: Sectors
        """
        if self.previous is None:
            return Sectors(0, self.size.sector_size)
        else:
            return self.previous.get_end()

    def map(self, device_path):
        """Maps the partition to a device_path

        :param str device_path: The device path this partition should be mapped to
        """
        self.fsm.map(device_path=device_path)

    def link_uuid(self):
        # /lib/udev/rules.d/60-kpartx.rules does not create symlinks in /dev/disk/by-{uuid,label}
        # This patch would fix that: http://www.redhat.com/archives/dm-devel/2013-July/msg00080.html
        # For now we just do the uuid part ourselves.
        # This is mainly to fix a problem in update-grub where /etc/grub.d/10_linux
        # checks if the $GRUB_DEVICE_UUID exists in /dev/disk/by-uuid and falls
        # back to $GRUB_DEVICE if it doesn't.
        # $GRUB_DEVICE is /dev/mapper/xvd{f,g...}# (on ec2), opposed to /dev/xvda# when booting.
        # Creating the symlink ensures that grub consistently uses
        # $GRUB_DEVICE_UUID when creating /boot/grub/grub.cfg
        self.disk_by_uuid_path = os.path.join('/dev/disk/by-uuid', self.get_uuid())
        if not os.path.exists(self.disk_by_uuid_path):
            os.symlink(self.device_path, self.disk_by_uuid_path)

    def unlink_uuid(self):
        if os.path.isfile(self.disk_by_uuid_path):
            os.remove(self.disk_by_uuid_path)
        self.disk_by_uuid_path = None

    def _before_create(self, e):
        """Creates the partition
        """
        from bootstrapvz.common.tools import log_check_call
        # The create command is fairly simple:
        # - fs_type is the partition filesystem, as defined by parted:
        #   fs-type can be one of "fat16", "fat32", "ext2", "HFS", "linux-swap",
        #   "NTFS", "reiserfs", or "ufs".
        # - start and end are just Bytes objects coerced into strings
        if self.filesystem == 'swap':
            fs_type = 'linux-swap'
        else:
            fs_type = 'ext2'
        create_command = ('mkpart primary {fs_type} {start} {end}'
                          .format(fs_type=fs_type,
                                  start=str(self.get_start() + self.pad_start),
                                  end=str(self.get_end() - self.pad_end)))
        # Create the partition
        log_check_call(['parted', '--script', '--align', 'none', e.volume.device_path,
                        '--', create_command])

        # Set any flags on the partition
        for flag in self.flags:
            log_check_call(['parted', '--script', e.volume.device_path,
                            '--', ('set {idx} {flag} on'
                                   .format(idx=str(self.get_index()), flag=flag))])

    def _before_map(self, e):
        # Set the device path
        self.device_path = e.device_path
        if e.src == 'unmapped_fmt':
            # Only link the uuid if the partition is formatted
            self.link_uuid()

    def _after_format(self, e):
        # We do this after formatting because there otherwise would be no UUID
        self.link_uuid()

    def _before_unmap(self, e):
        # When unmapped, the device_path information becomes invalid, so we delete it
        self.device_path = None
        if e.src == 'formatted':
            self.unlink_uuid()
