# Copyright 2012 Nebula, 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.

"""
Views for managing volumes.
"""

from collections import OrderedDict
import json

from django import shortcuts
from django.template.defaultfilters import slugify
from django.urls import reverse
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.utils import encoding
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.cache import cache_control
from django.views.decorators.cache import never_cache
from django.views import generic

from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon import tabs
from horizon.utils import memoized

from openstack_dashboard.api import cinder
from openstack_dashboard.api import nova
from openstack_dashboard import exceptions as dashboard_exception
from openstack_dashboard.usage import quotas
from openstack_dashboard.utils import filters
from openstack_dashboard.utils import futurist_utils

from openstack_dashboard.dashboards.project.volumes \
    import forms as volume_forms
from openstack_dashboard.dashboards.project.volumes \
    import tables as volume_tables
from openstack_dashboard.dashboards.project.volumes \
    import tabs as project_tabs


class VolumeTableMixIn(object):
    _has_more_data = False
    _has_prev_data = False

    def _get_volumes(self, search_opts=None):
        try:
            marker, sort_dir = self._get_marker()
            volumes, self._has_more_data, self._has_prev_data = \
                cinder.volume_list_paged(self.request, marker=marker,
                                         search_opts=search_opts,
                                         sort_dir=sort_dir, paginate=True)
            return volumes
        except Exception:
            exceptions.handle(self.request,
                              _('Unable to retrieve volume list.'))
            return []

    def _get_instances(self, search_opts=None):
        try:
            # TODO(tsufiev): we should pass attached_instance_ids to
            # nova.server_list as soon as Nova API allows for this
            instances, has_more = nova.server_list(self.request,
                                                   search_opts=search_opts)
            return instances
        except Exception:
            exceptions.handle(self.request,
                              _("Unable to retrieve volume/instance "
                                "attachment information"))
            return []

    def _get_volumes_ids_with_snapshots(self, search_opts=None):
        try:
            volume_ids = []
            snapshots = cinder.volume_snapshot_list(
                self.request, search_opts=search_opts)
            if snapshots:
                # extract out the volume ids
                volume_ids = set(s.volume_id for s in snapshots)
        except Exception:
            exceptions.handle(self.request,
                              _("Unable to retrieve snapshot list."))

        return volume_ids

    def _get_attached_instance_ids(self, volumes):
        attached_instance_ids = []
        for volume in volumes:
            for att in volume.attachments:
                server_id = att.get('server_id', None)
                if server_id is not None:
                    attached_instance_ids.append(server_id)
        return attached_instance_ids

    def _get_groups(self, volumes, search_opts=None):
        needs_group = False
        if volumes and hasattr(volumes[0], 'group_id'):
            needs_group = True
        if needs_group:
            try:
                groups_list = cinder.group_list(self.request,
                                                search_opts=search_opts)
                groups = dict((g.id, g) for g in groups_list)
            except Exception:
                groups = {}
                exceptions.handle(self.request,
                                  _("Unable to retrieve volume groups"))
        for volume in volumes:
            if needs_group:
                volume.group = groups.get(volume.group_id)
            else:
                volume.group = None

    # set attachment string and if volume has snapshots
    def _set_volume_attributes(self,
                               volumes,
                               instances,
                               volume_ids_with_snapshots):
        instances = OrderedDict([(inst.id, inst) for inst in instances])
        for volume in volumes:
            if volume_ids_with_snapshots:
                if volume.id in volume_ids_with_snapshots:
                    setattr(volume, 'has_snapshot', True)
            if instances:
                for att in volume.attachments:
                    server_id = att.get('server_id', None)
                    att['instance'] = instances.get(server_id, None)


class VolumesView(tables.PagedTableMixin, VolumeTableMixIn,
                  tables.DataTableView):
    table_class = volume_tables.VolumesTable
    page_title = _("Volumes")

    def get_data(self):
        volumes = []
        attached_instance_ids = []
        instances = []
        volume_ids_with_snapshots = []

        def _task_get_volumes():
            volumes.extend(self._get_volumes())
            attached_instance_ids.extend(
                self._get_attached_instance_ids(volumes))

        def _task_get_instances():
            # As long as Nova API does not allow passing attached_instance_ids
            # to nova.server_list, this call can be forged to pass anything
            # != None
            instances.extend(self._get_instances())

            # In volumes tab we don't need to know about the assignment
            # instance-image, therefore fixing it to an empty value
            for instance in instances:
                if hasattr(instance, 'image'):
                    if isinstance(instance.image, dict):
                        instance.image['name'] = "-"

        def _task_get_volumes_snapshots():
            volume_ids_with_snapshots.extend(
                self._get_volumes_ids_with_snapshots())

        futurist_utils.call_functions_parallel(
            _task_get_volumes,
            _task_get_instances,
            _task_get_volumes_snapshots)

        self._set_volume_attributes(