# Copyright 2014 Mirantis, Inc.
#
# 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.

from fuel_agent import errors
from fuel_agent.utils import utils


def info(dev):
    result = utils.execute('parted', '-s', dev, '-m',
                           'unit', 'MiB',
                           'print', 'free',
                           check_exit_code=[0, 1])
    lines = result[0].split('\n')
    generic_params = lines[1].rstrip(';').split(':')
    generic = {
        'dev': generic_params[0],
        'size': utils.parse_unit(generic_params[1], 'MiB'),
        'logical_block': int(generic_params[3]),
        'physical_block': int(generic_params[4]),
        'table': generic_params[5],
        'model': generic_params[6]
    }
    parts = []
    for line in lines[2:]:
        line = line.strip().rstrip(';')
        if not line:
            continue
        part_params = line.split(':')
        parts.append({
            'num': int(part_params[0]),
            'begin': utils.parse_unit(part_params[1], 'MiB'),
            'end': utils.parse_unit(part_params[2], 'MiB'),
            'size': utils.parse_unit(part_params[3], 'MiB'),
            'fstype': part_params[4] or None
        })
    return {'generic': generic, 'parts': parts}


def wipe(dev):
    # making an empty new table is equivalent to wiping the old one
    make_label(dev)


def make_label(dev, label='gpt'):
    """Creates partition label on a device.

    :param dev: A device file, e.g. /dev/sda.
    :param label: Partition label type 'gpt' or 'msdos'. Optional.

    :returns: None
    """
    if label not in ('gpt', 'msdos'):
        raise errors.WrongPartitionLabelError(
            'Wrong partition label type: %s' % label)
    utils.execute('parted', '-s', dev, 'mklabel', label,
                  check_exit_code=[0])


def set_partition_flag(dev, num, flag, state='on'):
    """Sets flag on a partition

    :param dev: A device file, e.g. /dev/sda.
    :param num: Partition number
    :param flag: Flag name. Must be one of 'bios_grub', 'legacy_boot',
    'boot', 'raid', 'lvm'
    :param state: Desiable flag state. 'on' or 'off'. Default is 'on'.

    :returns: None
    """
    # parted supports more flags but we are interested in
    # setting only this subset of them.
    # not all of these flags are compatible with one another.
    if flag not in ('bios_grub', 'legacy_boot', 'boot', 'raid', 'lvm'):
        raise errors.WrongPartitionSchemeError(
            'Unsupported partition flag: %s' % flag)
    if state not in ('on', 'off'):
        raise errors.WrongPartitionSchemeError(
            'Wrong partition flag state: %s' % state)
    utils.execute('parted', '-s', dev, 'set', str(num),
                  flag, state, check_exit_code=[0])


def set_gpt_type(dev, num, type_guid):
    """Sets guid on a partition.

    :param dev: A device file, e.g. /dev/sda.
    :param num: Partition number
    :param type_guid: Partition type guid. Must be one of those listed
    on this page http://en.wikipedia.org/wiki/GUID_Partition_Table.
    This method does not check whether type_guid is valid or not.

    :returns: None
    """
    # TODO(kozhukalov): check whether type_guid is valid
    utils.execute('sgdisk', '--typecode=%s:%s' % (num, type_guid),
                  dev, check_exit_code=[0])


def make_partition(dev, begin, end, ptype):
    if ptype not in ('primary', 'logical'):
        raise errors.WrongPartitionSchemeError(
            'Wrong partition type: %s' % ptype)

    # check begin >= end
    if begin >= end:
        raise errors.WrongPartitionSchemeError(
            'Wrong boundaries: begin >= end')

    # check if begin and end are inside one of free spaces available
    if not any(x['fstype'] == 'free' and begin >= x['begin'] and
               end <= x['end'] for x in info(dev)['parts']):
        raise errors.WrongPartitionSchemeError(
            'Invalid boundaries: begin and end '
            'are not inside available free space'
        )

    utils.execute('parted', '-a', 'optimal', '-s', dev, 'unit', 'MiB',
                  'mkpart', ptype, str(begin), str(end), check_exit_code=[0])


def remove_partition(dev, num):
    if not any(x['fstype'] != 'free' and x['num'] == num
               for x in info(dev)['parts']):
        raise errors.PartitionNotFoundError('Partition %s not found' % num)
    utils.execute('parted', '-s', dev, 'rm', str(num), check_exit_code=[0])
