#    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 functools
import sys

from django.utils.translation import ugettext_lazy as _

import horizon

from manila_ui.api import manila

from openstack_dashboard.api import base
from openstack_dashboard.dashboards.admin.defaults import tables as quota_tbl
from openstack_dashboard.dashboards.admin.defaults import views \
    as default_views
from openstack_dashboard.dashboards.admin.defaults import workflows \
    as default_workflows
from openstack_dashboard.dashboards.identity.projects import views \
    as project_views
from openstack_dashboard.dashboards.identity.projects import workflows \
    as project_workflows
from openstack_dashboard.dashboards.project.overview import views \
    as overview_views
from openstack_dashboard.usage import base as usage_base
from openstack_dashboard.usage import quotas


def wrap(orig_func):
    """decorator to wrap an existing function
       Modified post from http://downgra.de/2009/05/16/python-monkey-patching/
       to work with functions
       e.g.

       @wrap(quotas.tenant_limit_usages)
       def tenant_limit_usages(orig, self):
           limits = orig(request)
           limits['disksUsed'] = 100
           return limits

       the first parameter of the new function is the the original,
       overwritten function ('orig').
    """

    def outer(new_func):
        @functools.wraps(orig_func)
        def wrapper(*args, **kwargs):
            return new_func(orig_func, *args, **kwargs)
        # Replace the original function in the module with the wrapper
        orig_module = sys.modules[orig_func.__module__]
        setattr(orig_module, orig_func.__name__, wrapper)
        return wrapper
    return outer


# All public methods in openstack_dashboard.usage.quotas are hardcoded to
# only look at a fixed set of services (nova, cinder, etc.).  In order to
# incorporate manila quotas and usage, all public functions must be
# monkey-patched to add that information in.

MANILA_QUOTA_FIELDS = (
    "shares",
    "snapshots",
    "gigabytes",
    "share_networks",
)
MANILA_QUOTA_NAMES = {
    'shares': _('Shares'),
    'share_networks': _('Shares Networks'),
}

quotas.QUOTA_FIELDS = quotas.QUOTA_FIELDS + MANILA_QUOTA_FIELDS


def _get_manila_disabled_quotas(request):
    disabled_quotas = []
    if not base.is_service_enabled(request, 'share'):
        disabled_quotas.extend(MANILA_QUOTA_FIELDS)

    return disabled_quotas


def _get_manila_quota_data(request, method_name, disabled_quotas=None,
                           tenant_id=None):
    if not tenant_id:
        tenant_id = request.user.tenant_id
    if disabled_quotas is None:
        disabled_quotas = _get_manila_disabled_quotas(request)
    if 'shares' not in disabled_quotas:
        return getattr(manila, method_name)(request, tenant_id)
    else:
        return None


@wrap(quotas.get_default_quota_data)
def get_default_quota_data(f, request, disabled_quotas=None, tenant_id=None):
    qs = f(request, disabled_quotas, tenant_id)
    manila_quota = _get_manila_quota_data(request, "default_quota_get",
                                          disabled_quotas=disabled_quotas,
                                          tenant_id=tenant_id)
    if manila_quota:
        qs.add(manila_quota)
    return qs


@wrap(quotas.get_tenant_quota_data)
def get_tenant_quota_data(f, request, disabled_quotas=None, tenant_id=None):
    qs = f(request, disabled_quotas, tenant_id)
    manila_quota = _get_manila_quota_data(request, "tenant_quota_get",
                                          disabled_quotas=disabled_quotas,
                                          tenant_id=tenant_id)
    if manila_quota:
        qs.add(manila_quota)
    return qs


@wrap(quotas.get_disabled_quotas)
def get_disabled_quotas(f, request):
    disabled_quotas = f(request)
    disabled_quotas.update(_get_manila_disabled_quotas(request))
    return disabled_quotas


@wrap(quotas.tenant_quota_usages)
def tenant_quota_usages(f, request, tenant_id=None):

    usages = f(request, tenant_id)

    if 'shares' not in _get_manila_disabled_quotas(request):
        shares = manila.share_list(request)
        snapshots = manila.share_snapshot_list(request)
        sn_l = manila.share_network_list(request)
        gig_s = sum([int(v.size) for v in shares])
        gig_ss = sum([int(v.size) for v in snapshots])
        usages.tally('gigabytes', gig_s + gig_ss)
        usages.tally('shares', len(shares))
        usages.tally('snapshots', len(snapshots))
        usages.tally('share_networks', len(sn_l))

    return usages


@wrap(quotas.tenant_limit_usages)
def tenant_limit_usages(f, request):

    limits = f(request)

    if base.is_service_enabled(request, 'share'):
        try:
            limits.update(manila.tenant_absolute_limits(request))
            shares = manila.share_list(request)
            snapshots = manila.share_snapshot_list(request)
            total_s_size = sum([getattr(share, 'size', 0) for share in shares])
            total_ss_size = sum([getattr(ss, 'size', 0) for ss in snapshots])
            limits['totalGigabytesUsed'] = total_s_size + total_ss_size
            limits['totalSharesUsed'] = len(shares)
            limits['totalSnapshotsUsed'] = len(snapshots)
        except Exception:
            msg = _("Unable to retrieve share limit information.")
            horizon.exceptions.handle(request, msg)

    return limits


@wrap(quota_tbl.get_quota_name)
def get_quota_name(f, quota):
    if quota.name in MANILA_QUOTA_NAMES:
        return MANILA_QUOTA_NAMES.get(quota.name)
    else:
        return f(quota)


#
# Add manila fields to Admin/Defaults/Update Defaults
#


class ManilaUpdateDefaultQuotaAction(
        default_workflows.UpdateDefaultQuotasAction):
    shares = horizon.forms.IntegerField(min_value=-1, label=_("Shares"))
    share_networks = horizon.forms.IntegerField(min_value=-1,
                                                label=_("Share Networks"))

    class Meta(object):
        name = _("Default Quota")
        slug = 'update_default_quotas'
        help_text = _("From here you can update the default quotas "
                      "(max limits).")


class ManilaUpdateDefaultQuotasStep(default_workflows.UpdateDefaultQuotasStep):
    action_class = ManilaUpdateDefaultQuotaAction
    contributes = quotas.QUOTA_FIELDS


class ManilaUpdateDefaultQuotas(default_workflows.UpdateDefaultQuotas):
    default_steps = (ManilaUpdateDefaultQuotasStep,)

    def handle(self, request, data):
        try:
            super(ManilaUpdateDefaultQuotas, self).handle(request, data)

            if base.is_service_enabled(request, 'share'):
                manila_data = dict([(key, data[key]) for key in
                                    MANILA_QUOTA_FIELDS])
                manila.default_quota_update(request, **manila_data)

        except Exception:
            horizon.exceptions.handle(request,
                                      _('Unable to update default quotas.'))

        return True


default_views.UpdateDefaultQuotasView.workflow_class = \
    ManilaUpdateDefaultQuotas

#
# Add manila fields to Identity/Projects/Modify Quotas
#


class ManilaUpdateProjectQuotaAction(
        project_workflows.UpdateProjectQuotaAction):
    shares = horizon.forms.IntegerField(min_value=-1, label=_("Shares"))
    share_networks = horizon.forms.IntegerField(min_value=-1,
                                                label=_("Share Networks"))

    class Meta(object):
        name = _("Quota")
        slug = 'update_quotas'
        help_text = _("Set maximum quotas for the project.")

project_workflows.UpdateProjectQuota.action_class = \
    ManilaUpdateProjectQuotaAction
project_workflows.UpdateProjectQuota.contributes = quotas.QUOTA_FIELDS


class ManilaUpdateProject(project_workflows.UpdateProject):
    def handle(self, request, data):
        try:
            super(ManilaUpdateProject, self).handle(request, data)

            if base.is_service_enabled(request, 'share'):
                manila_data = dict([(key, data[key]) for key in
                                    MANILA_QUOTA_FIELDS])
                manila.tenant_quota_update(request,
                                           data['project_id'],
                                           **manila_data)

        except Exception:
            horizon.exceptions.handle(request,
                                      _('Modified project information and '
                                        'members, but unable to modify '
                                        'project quotas.'))
        return True

project_views.UpdateProjectView.workflow_class = ManilaUpdateProject

#
# Add manila fields to Identity/Projects/Create Project
#


class ManilaCreateProjectQuotaAction(
        project_workflows.CreateProjectQuotaAction):
    shares = horizon.forms.IntegerField(min_value=-1, label=_("Shares"))
    share_networks = horizon.forms.IntegerField(min_value=-1,
                                                label=_("Share Networks"))

    class Meta(object):
        name = _("Quota")
        slug = 'create_quotas'
        help_text = _("Set maximum quotas for the project.")

project_workflows.CreateProjectQuota.action_class = \
    ManilaCreateProjectQuotaAction
project_workflows.CreateProjectQuota.contributes = quotas.QUOTA_FIELDS


class ManilaCreateProject(project_workflows.CreateProject):
    def handle(self, request, data):
        try:
            super(ManilaCreateProject, self).handle(request, data)

            if base.is_service_enabled(request, 'share'):
                manila_data = dict([(key, data[key]) for key in
                                    MANILA_QUOTA_FIELDS])
                manila.tenant_quota_update(request,
                                           self.object.id,
                                           **manila_data)

        except Exception:
            horizon.exceptions.handle(request,
                                      _('Unable to set project quotas.'))
        return True

project_views.CreateProjectView.workflow_class = ManilaCreateProject

#
# Add extra pie charts to Project/Compute Overview
#


class ManilaUsage(usage_base.ProjectUsage):

    def get_manila_limits(self):
        """Get share limits if manila is enabled."""
        if not base.is_service_enabled(self.request, 'share'):
            return
        try:
            self.limits.update(manila.tenant_absolute_limits(self.request))
        except Exception:
            msg = _("Unable to retrieve share limit information.")
            horizon.exceptions.handle(self.request, msg)
        return

    def get_limits(self):
        super(self.__class__, self).get_limits()
        self.get_manila_limits()


def get_context_data(self, **kwargs):
    context = super(self.__class__, self).get_context_data(**kwargs)
    types = (
        ("totalSharesUsed", "maxTotalShares", _("Shares")),
        ("totalShareGigabytesUsed", "maxTotalShareGigabytes",
         _("Share Storage")),
        ("totalShareSnapshotsUsed", "maxTotalShareSnapshots",
         _("Share Snapshots")),
        ("totalSnapshotGigabytesUsed", "maxTotalSnapshotGigabytes",
         _("Share Snapshots Storage")),
        ("totalShareNetworksUsed", "maxTotalShareNetworks",
         _("Share Networks")),
    )
    for t in types:
        if t[0] in self.usage.limits and t[1] in self.usage.limits:
            context['charts'].append({
                'name': t[2],
                'used': self.usage.limits[t[0]],
                'max': self.usage.limits[t[1]],
                'text': False,
            })
    return context


overview_views.ProjectOverview.get_context_data = get_context_data
overview_views.ProjectOverview.usage_class = ManilaUsage
