from bootstrapvz.base import Task
from .. import phases
from ..tools import log_check_call
import bootstrap
import host
import volume


class AddRequiredCommands(Task):
    description = 'Adding commands required for formatting'
    phase = phases.validation
    successors = [host.CheckExternalCommands]

    @classmethod
    def run(cls, info):
        if 'xfs' in (p.filesystem for p in info.volume.partition_map.partitions):
            info.host_dependencies['mkfs.xfs'] = 'xfsprogs'


class Format(Task):
    description = 'Formatting the volume'
    phase = phases.volume_preparation

    @classmethod
    def run(cls, info):
        from bootstrapvz.base.fs.partitions.unformatted import UnformattedPartition
        for partition in info.volume.partition_map.partitions:
            if isinstance(partition, UnformattedPartition):
                continue
            partition.format()


class TuneVolumeFS(Task):
    description = 'Tuning the bootstrap volume filesystem'
    phase = phases.volume_preparation
    predecessors = [Format]

    @classmethod
    def run(cls, info):
        from bootstrapvz.base.fs.partitions.unformatted import UnformattedPartition
        import re
        # Disable the time based filesystem check
        for partition in info.volume.partition_map.partitions:
            if isinstance(partition, UnformattedPartition):
                continue
            if re.match('^ext[2-4]$', partition.filesystem) is not None:
                log_check_call(['tune2fs', '-i', '0', partition.device_path])


class AddXFSProgs(Task):
    description = 'Adding `xfsprogs\' to the image packages'
    phase = phases.preparation

    @classmethod
    def run(cls, info):
        info.packages.add('xfsprogs')


class CreateMountDir(Task):
    description = 'Creating mountpoint for the root partition'
    phase = phases.volume_mounting

    @classmethod
    def run(cls, info):
        import os
        info.root = os.path.join(info.workspace, 'root')
        os.makedirs(info.root)


class MountRoot(Task):
    description = 'Mounting the root partition'
    phase = phases.volume_mounting
    predecessors = [CreateMountDir]

    @classmethod
    def run(cls, info):
        info.volume.partition_map.root.mount(destination=info.root)


class CreateBootMountDir(Task):
    description = 'Creating mountpoint for the boot partition'
    phase = phases.volume_mounting
    predecessors = [MountRoot]

    @classmethod
    def run(cls, info):
        import os.path
        os.makedirs(os.path.join(info.root, 'boot'))


class MountBoot(Task):
    description = 'Mounting the boot partition'
    phase = phases.volume_mounting
    predecessors = [CreateBootMountDir]

    @classmethod
    def run(cls, info):
        p_map = info.volume.partition_map
        p_map.root.add_mount(p_map.boot, 'boot')


class MountAdditional(Task):
    description = 'Mounting additional partitions'
    phase = phases.volume_mounting
    predecessors = [MountRoot]

    @classmethod
    def run(cls, info):
        import os
        from bootstrapvz.base.fs.partitions.unformatted import UnformattedPartition
        from bootstrapvz.base.fs.partitions.single import SinglePartition

        def is_additional(partition):
            return (not isinstance(partition, (UnformattedPartition, SinglePartition)) and
                    partition.name not in ["boot", "swap", "root"])

        p_map = info.volume.partition_map
        partitions = p_map.partitions
        for partition in sorted(
                filter(is_additional, partitions),
                key=lambda partition: len(partition.name)):
            partition = getattr(p_map, partition.name)
            os.makedirs(os.path.join(info.root, partition.name))
            if partition.mountopts is None:
                p_map.root.add_mount(getattr(p_map, partition.name), partition.name)
            else:
                p_map.root.add_mount(getattr(p_map, partition.name), partition.name, ['--options'] + partition.mountopts)


class MountSpecials(Task):
    description = 'Mounting special block devices'
    phase = phases.os_installation
    predecessors = [bootstrap.Bootstrap]

    @classmethod
    def run(cls, info):
        root = info.volume.partition_map.root
        root.add_mount('/dev', 'dev', ['--bind'])
        root.add_mount('none', 'proc', ['--types', 'proc'])
        root.add_mount('none', 'sys', ['--types', 'sysfs'])
        root.add_mount('none', 'dev/pts', ['--types', 'devpts'])


class CopyMountTable(Task):
    description = 'Copying mtab from host system'
    phase = phases.os_installation
    predecessors = [MountSpecials]

    @classmethod
    def run(cls, info):
        import shutil
        import os.path
        shutil.copy('/proc/mounts', os.path.join(info.root, 'etc/mtab'))


class UnmountRoot(Task):
    description = 'Unmounting the bootstrap volume'
    phase = phases.volume_unmounting
    successors = [volume.Detach]

    @classmethod
    def run(cls, info):
        info.volume.partition_map.root.unmount()


class RemoveMountTable(Task):
    description = 'Removing mtab'
    phase = phases.volume_unmounting
    successors = [UnmountRoot]

    @classmethod
    def run(cls, info):
        import os
        os.remove(os.path.join(info.root, 'etc/mtab'))


class DeleteMountDir(Task):
    description = 'Deleting mountpoint for the bootstrap volume'
    phase = phases.volume_unmounting
    predecessors = [UnmountRoot]

    @classmethod
    def run(cls, info):
        import os
        os.rmdir(info.root)
        del info.root


class FStab(Task):
    description = 'Adding partitions to the fstab'
    phase = phases.system_modification

    @classmethod
    def run(cls, info):
        import os.path
        from bootstrapvz.base.fs.partitions.unformatted import UnformattedPartition
        from bootstrapvz.base.fs.partitions.single import SinglePartition

        def is_additional(partition):
            return (not isinstance(partition, (UnformattedPartition, SinglePartition)) and
                    partition.name not in ["boot", "swap", "root"])

        p_map = info.volume.partition_map
        partitions = p_map.partitions
        mount_points = [{'path': '/',
                         'partition': p_map.root,
                         'dump': '1',
                         'pass_num': '1',
                         }]
        if hasattr(p_map, 'boot'):
            mount_points.append({'path': '/boot',
                                 'partition': p_map.boot,
                                 'dump': '1',
                                 'pass_num': '2',
                                 })
        if hasattr(p_map, 'swap'):
            mount_points.append({'path': 'none',
                                 'partition': p_map.swap,
                                 'dump': '1',
                                 'pass_num': '0',
                                 })

        for partition in sorted(
                filter(is_additional, partitions),
                key=lambda partition: len(partition.name)):
            mount_points.append({'path': "/" + partition.name,
                                 'partition': getattr(p_map, partition.name),
                                 'dump': '1',
                                 'pass_num': '2',
                                 })
        fstab_lines = []
        for mount_point in mount_points:
            partition = mount_point['partition']
            if partition.mountopts is None:
                mount_opts = ['defaults']
            else:
                mount_opts = partition.mountopts
            fstab_lines.append('UUID={uuid} {mountpoint} {filesystem} {mount_opts} {dump} {pass_num}'
                               .format(uuid=partition.get_uuid(),
                                       mountpoint=mount_point['path'],
                                       filesystem=partition.filesystem,
                                       mount_opts=','.join(mount_opts),
                                       dump=mount_point['dump'],
                                       pass_num=mount_point['pass_num']))

        fstab_path = os.path.join(info.root, 'etc/fstab')
        with open(fstab_path, 'w') as fstab:
            fstab.write('\n'.join(fstab_lines))
            fstab.write('\n')
