# All Rights Reserved.
#
#    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 time

import prettytable

from cinderclient import exceptions
from cinderclient import utils

_quota_resources = ['volumes', 'snapshots', 'gigabytes',
                    'backups', 'backup_gigabytes',
                    'per_volume_gigabytes', 'groups', ]
_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit', 'Allocated']


def _print(pt, order):
    print(pt.get_string(sortby=order))


def _pretty_format_dict(data_dict):
    formatted_data = []

    for k in sorted(data_dict):
        formatted_data.append("%s : %s" % (k, data_dict[k]))

    return "\n".join(formatted_data)


def print_list(objs, fields, exclude_unavailable=False, formatters=None,
               sortby_index=0):
    '''Prints a list of objects.

    @param objs: Objects to print
    @param fields: Fields on each object to be printed
    @param exclude_unavailable: Boolean to decide if unavailable fields are
                                removed
    @param formatters: Custom field formatters
    @param sortby_index: Results sorted against the key in the fields list at
                         this index; if None then the object order is not
                         altered
    '''
    formatters = formatters or {}
    mixed_case_fields = ['serverId']
    removed_fields = []
    rows = []

    for o in objs:
        row = []
        for field in fields:
            if field in removed_fields:
                continue
            if field in formatters:
                row.append(formatters[field](o))
            else:
                if field in mixed_case_fields:
                    field_name = field.replace(' ', '_')
                else:
                    field_name = field.lower().replace(' ', '_')
                if isinstance(o, dict) and field in o:
                    data = o[field]
                else:
                    if not hasattr(o, field_name) and exclude_unavailable:
                        removed_fields.append(field)
                        continue
                    else:
                        data = getattr(o, field_name, '')
                if data is None:
                    data = '-'
                if isinstance(data, str) and "\r" in data:
                    data = data.replace("\r", " ")
                row.append(data)
        rows.append(row)

    for f in removed_fields:
        fields.remove(f)

    pt = prettytable.PrettyTable((f for f in fields), caching=False)
    pt.align = 'l'
    for row in rows:
        count = 0
        # Converts unicode values in dictionary to string
        for part in row:
            count = count + 1
            if isinstance(part, dict):
                row[count - 1] = part
        pt.add_row(row)

    if sortby_index is None:
        order_by = None
    else:
        order_by = fields[sortby_index]
    _print(pt, order_by)


def print_dict(d, property="Property", formatters=None):
    pt = prettytable.PrettyTable([property, 'Value'], caching=False)
    pt.align = 'l'
    formatters = formatters or {}

    for r in d.items():
        r = list(r)

        if r[0] in formatters:
            if isinstance(r[1], dict):
                r[1] = _pretty_format_dict(r[1])
        if isinstance(r[1], str) and "\r" in r[1]:
            r[1] = r[1].replace("\r", " ")
        pt.add_row(r)
    _print(pt, property)


def print_volume_image(image_resp_tuple):
    # image_resp_tuple = tuple (response, body)
    image = image_resp_tuple[1]
    vt = image['os-volume_upload_image'].get('volume_type')
    if vt is not None:
        image['os-volume_upload_image']['volume_type'] = vt.get('name')
    print_dict(image['os-volume_upload_image'])


def poll_for_status(poll_fn, obj_id, action, final_ok_states,
                    poll_period=5, show_progress=True):
    """Blocks while an action occurs. Periodically shows progress."""
    def print_progress(progress):
        if show_progress:
            msg = ('\rInstance %(action)s... %(progress)s%% complete'
                   % dict(action=action, progress=progress))
        else:
            msg = '\rInstance %(action)s...' % dict(action=action)

        sys.stdout.write(msg)
        sys.stdout.flush()

    print()
    while True:
        obj = poll_fn(obj_id)
        status = obj.status.lower()
        progress = getattr(obj, 'progress', None) or 0
        if status in final_ok_states:
            print_progress(100)
            print("\nFinished")
            break
        elif status == "error":
            print("\nError %(action)s instance" % {'action': action})
            break
        else:
            print_progress(progress)
            time.sleep(poll_period)


def find_volume_snapshot(cs, snapshot):
    """Gets a volume snapshot by name or ID."""
    return utils.find_resource(cs.volume_snapshots, snapshot)


def find_vtype(cs, vtype):
    """Gets a volume type by name or ID."""
    return utils.find_resource(cs.volume_types, vtype)


def find_gtype(cs, gtype):
    """Gets a group type by name or ID."""
    return utils.find_resource(cs.group_types, gtype)


def find_backup(cs, backup):
    """Gets a backup by name or ID."""
    return utils.find_resource(cs.backups, backup)


def find_consistencygroup(cs, consistencygroup):
    """Gets a consistency group by name or ID."""
    return utils.find_resource(cs.consistencygroups, consistencygroup)


def find_group(cs, group, **kwargs):
    """Gets a group by name or ID."""
    kwargs['is_group'] = True
    return utils.find_resource(cs.groups, group, **kwargs)


def find_cgsnapshot(cs, cgsnapshot):
    """Gets a cgsnapshot by name or ID."""
    return utils.find_resource(cs.cgsnapshots, cgsnapshot)


def find_group_snapshot(cs, group_snapshot):
    """Gets a group_snapshot by name or ID."""
    return utils.find_resource(cs.group_snapshots, group_snapshot)


def find_transfer(cs, transfer):
    """Gets a transfer by name or ID."""
    return utils.find_resource(cs.transfers, transfer)


def find_qos_specs(cs, qos_specs):
    """Gets a qos specs by ID."""
    return utils.find_resource(cs.qos_specs, qos_specs)


def find_message(cs, message):
    """Gets a message by ID."""
    return utils.find_resource(cs.messages, message)


def print_volume_snapshot(snapshot):
    print_dict(snapshot._info)


def translate_keys(collection, convert):
    for item in collection:
        keys = item.__dict__
        for from_key, to_key in convert:
            if from_key in keys and to_key not in keys:
                setattr(item, to_key, item._info[from_key])


def translate_volume_keys(collection):
    convert = [('volumeType', 'volume_type'),
               ('os-vol-tenant-attr:tenant_id', 'tenant_id')]
    translate_keys(collection, convert)


def translate_volume_snapshot_keys(collection):
    convert = [('volumeId', 'volume_id')]
    translate_keys(collection, convert)


def translate_availability_zone_keys(collection):
    convert = [('zoneName', 'name'), ('zoneState', 'status')]
    translate_keys(collection, convert)


def extract_filters(args):
    filters = {}
    for f in args:
        if '=' in f:
            (key, value) = f.split('=', 1)
            if value.startswith('{') and value.endswith('}'):
                value = _build_internal_dict(value[1:-1])
            filters[key] = value
        else:
            print("WARNING: Ignoring the filter %s while showing result." % f)

    return filters


def _build_internal_dict(content):
    result = {}
    for pair in content.split(','):
        k, v = pair.split(':', 1)
        result.update({k.strip(): v.strip()})
    return result


def extract_metadata(args, type='user_metadata'):
    metadata = {}
    if type == 'image_metadata':
        args_metadata = args.image_metadata
    else:
        args_metadata = args.metadata
    for metadatum in args_metadata:
        # unset doesn't require a val, so we have the if/else
        if '=' in metadatum:
            (key, value) = metadatum.split('=', 1)
        else:
            key = metadatum
            value = None

        metadata[key] = value
    return metadata


def print_volume_type_list(vtypes):
    print_list(vtypes, ['ID', 'Name', 'Description', 'Is_Public'])


def print_group_type_list(gtypes):
    print_list(gtypes, ['ID', 'Name', 'Description'])


def print_resource_filter_list(filters):
    formatter = {'Filters': lambda resource: ', '.join(resource.filters)}
    print_list(filters, ['Resource', 'Filters'], formatters=formatter)


def quota_show(quotas):
    quotas_info_dict = quotas._info
    quota_dict = {}
    for resource in quotas_info_dict.keys():
        good_name = False
        for name in _quota_resources:
            if resource.startswith(name):
                good_name = True
        if not good_name:
            continue
        quota_dict[resource] = getattr(quotas, resource, None)
    print_dict(quota_dict)


def quota_usage_show(quotas):
    quota_list = []
    quotas_info_dict = quotas._info
    for resource in quotas_info_dict.keys():
        good_name = False
        for name in _quota_resources:
            if resource.startswith(name):
                good_name = True
        if not good_name:
            continue
        quota_info = getattr(quotas, resource, None)
        quota_info['Type'] = resource
        quota_info = dict((k.capitalize(), v) for k, v in quota_info.items())
        quota_list.append(quota_info)
    print_list(quota_list, _quota_infos)


def quota_update(manager, identifier, args):
    updates = {}
    for resource in _quota_resources:
        val = getattr(args, resource, None)
        if val is not None:
            if args.volume_type:
                resource = resource + '_%s' % args.volume_type
            updates[resource] = val

    if updates:
        skip_validation = getattr(args, 'skip_validation', True)
        if not skip_validation:
            updates['skip_validation'] = skip_validation
        quota_show(manager.update(identifier, **updates))
    else:
        msg = 'Must supply at least one quota field to update.'
        raise exceptions.ClientException(code=1, message=msg)


def find_volume_type(cs, vtype):
    """Gets a volume type by name or ID."""
    return utils.find_resource(cs.volume_types, vtype)


def find_group_type(cs, gtype):
    """Gets a group type by name or ID."""
    return utils.find_resource(cs.group_types, gtype)


def print_volume_encryption_type_list(encryption_types):
    """
    Lists volume encryption types.

    :param encryption_types: a list of :class: VolumeEncryptionType instances
    """
    print_list(encryption_types, ['Volume Type ID', 'Provider',
                                  'Cipher', 'Key Size',
                                  'Control Location'])


def print_qos_specs(qos_specs):
    # formatters defines field to be converted from unicode to string
    print_dict(qos_specs._info, formatters=['specs'])


def print_qos_specs_list(q_specs):
    print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])


def print_qos_specs_and_associations_list(q_specs):
    print_list(q_specs, ['ID', 'Name', 'Consumer', 'specs'])


def print_associations_list(associations):
    print_list(associations, ['Association_Type', 'Name', 'ID'])


def _poll_for_status(poll_fn, obj_id, info, action, final_ok_states,
                     timeout_period, global_request_id=None, messages=None,
                     poll_period=2, status_field="status"):
    """Block while an action is being performed."""
    time_elapsed = 0
    while True:
        time.sleep(poll_period)
        time_elapsed += poll_period
        obj = poll_fn(obj_id)
        status = getattr(obj, status_field)
        info[status_field] = status
        if status:
            status = status.lower()

        if status in final_ok_states:
            break
        elif status == "error":
            print_dict(info)
            if global_request_id:
                search_opts = {
                    'request_id': global_request_id
                    }
                message_list = messages.list(search_opts=search_opts)
                try:
                    fault_msg = message_list[0].user_message
                except IndexError:
                    fault_msg = "Unknown error. Operation failed."
                raise exceptions.ResourceInErrorState(obj, fault_msg)
        elif time_elapsed == timeout_period:
            print_dict(info)
            raise exceptions.TimeoutException(obj, action)
