# bijoe - BI dashboard
# Copyright (C) 2015  Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from collections import OrderedDict
import hashlib
import json

from django.conf import settings
from django.utils.text import slugify
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import MultipleObjectMixin
from django.views.generic import DetailView, ListView, View, TemplateView
from django.shortcuts import redirect
from django.core.urlresolvers import reverse, reverse_lazy
from django.http import HttpResponse, Http404
from django.core.exceptions import PermissionDenied
from django.views.decorators.clickjacking import xframe_options_exempt

from rest_framework import generics
from rest_framework.response import Response

from ..utils import get_warehouses
from ..engine import Engine
from . import models, forms, signature
from .utils import Visualization
from .. import views


class WarehouseView(views.AuthorizationMixin, TemplateView):
    template_name = 'bijoe/warehouse.html'

    def get_context_data(self, **kwargs):
        ctx = super(WarehouseView, self).get_context_data(**kwargs)
        try:
            warehouse = [warehouse for warehouse in get_warehouses(self.request)
                         if warehouse.name == self.kwargs['warehouse']][0]
        except IndexError:
            raise Http404
        ctx['warehouse'] = Engine(warehouse)
        ctx['cubes'] = sorted(ctx['warehouse'].cubes, key=lambda cube: cube.label.strip().lower())
        return ctx


class CubeDisplayMixin(object):
    def get_context_data(self, **kwargs):
        ctx = super(CubeDisplayMixin, self).get_context_data(**kwargs)
        ctx['warehouse'] = self.warehouse
        ctx['cube'] = self.cube
        ctx['visualization'] = self.visualization
        return ctx


class CubeMixin(object):
    def visualization(self, request, cube):
        self.form = forms.CubeForm(cube=self.cube, data=request.GET or request.POST)
        if self.form.is_valid():
            return Visualization.from_form(self.cube, self.form)

    def dispatch(self, request, *args, **kwargs):
        try:
            self.warehouse = Engine([warehouse for warehouse in get_warehouses(self.request)
                                     if warehouse.name == self.kwargs['warehouse']][0])
        except IndexError:
            raise Http404
        try:
            self.cube = self.warehouse[self.kwargs['cube']]
        except KeyError:
            raise Http404
        self.visualization = self.visualization(request, cube)
        return super(CubeMixin, self).dispatch(request, *args, **kwargs)


class CubeView(views.AuthorizationMixin, CubeDisplayMixin, CubeMixin, TemplateView):
    template_name = 'bijoe/cube.html'

    def post(self, request, *args, **kwargs):
        if 'ods' in self.request.POST:
            return self.ods(self.visualization)
        return self.get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        ctx = super(CubeView, self).get_context_data(**kwargs)
        ctx['form'] = self.form
        return ctx


class CreateVisualizationView(views.AuthorizationMixin, CubeMixin, CreateView):
    model = models.Visualization
    form_class = forms.VisualizationForm
    template_name = 'bijoe/create_visualization.html'
    success_url = '/visualization/{id}/'

    def get(self, request, *args, **kwargs):
        if not self.visualization:
            return redirect('homepage')
        return super(CreateVisualizationView, self).get(request, *args, **kwargs)

    def form_valid(self, form):
        if not self.visualization:
            return redirect('homepage')
        form.instance.parameters = self.visualization.to_json()
        return super(CreateVisualizationView, self).form_valid(form)


class VisualizationView(views.AuthorizationMixin, CubeDisplayMixin, DetailView):
    model = models.Visualization
    template_name = 'bijoe/visualization.html'

    def get_object(self):
        named_visualization = super(VisualizationView, self).get_object()
        if not hasattr(self, 'visualization'):
            self.visualization = Visualization.from_json(named_visualization.parameters,
                                                         request=self.request)
        self.cube = self.visualization.cube
        self.warehouse = self.cube.engine
        return named_visualization

    def post(self, request, *args, **kwargs):
        named_visualization = self.get_object()
        if 'ods' in self.request.POST:
            return self.ods(self.visualization)
        form = forms.CubeForm(cube=self.cube, data=request.POST)
        if form.is_valid():
            self.visualization = Visualization.from_form(self.cube, form)
            named_visualization.parameters = self.visualization.to_json()
            if 'save' in request.POST:
                named_visualization.save()
        return self.get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        ctx = super(VisualizationView, self).get_context_data(**kwargs)
        initial = {
            'representation': self.visualization.representation,
            'measure': self.visualization.measure.name,
            'loop': self.visualization.loop and self.visualization.loop.name,
            'drilldown_x': self.visualization.drilldown_x and self.visualization.drilldown_x.name,
            'drilldown_y': self.visualization.drilldown_y and self.visualization.drilldown_y.name,
        }
        for key, value in self.visualization.filters.iteritems() or []:
            if isinstance(value, list):
                value = tuple(value)
            initial['filter__%s' % key] = value
        ctx['form'] = forms.CubeForm(cube=self.cube, initial=initial)
        path = reverse('visualization-iframe', args=self.args, kwargs=self.kwargs)
        signature = hashlib.sha1(path + settings.SECRET_KEY).hexdigest()
        path += '?signature=' + signature
        ctx['iframe_url'] = path
        return ctx


class SignatureAuthorizationMixin(views.AuthorizationMixin):
    def authorize(self, request):
        if request.user.is_authenticated() and request.user.is_superuser:
            return True
        if 'signature' in request.GET:
            signature = hashlib.sha1(request.path + settings.SECRET_KEY).hexdigest()
            if request.GET['signature'] == signature:
                return True
        return False


class VisualizationIframeView(SignatureAuthorizationMixin, VisualizationView):
    template_name = 'bijoe/cube_raw.html'


class DeleteVisualizationView(views.AuthorizationMixin, DeleteView):
    model = models.Visualization
    template_name = 'bijoe/delete_visualization.html'
    success_url = reverse_lazy('homepage')


class VisualizationsView(views.AuthorizationMixin, ListView):
    template_name = 'bijoe/visualizations.html'
    model = models.Visualization


class RenameVisualization(views.AuthorizationMixin, UpdateView):
    model = models.Visualization
    form_class = forms.VisualizationForm
    template_name = 'bijoe/rename_visualization.html'
    success_url = '/visualization/{id}/'


class VisualizationsJSONView(MultipleObjectMixin, View):
    model = models.Visualization

    def get(self, request, *args, **kwargs):
        if not request.user.is_authenticated() or not request.user.is_superuser:
            known_services = getattr(settings, 'KNOWN_SERVICES', [])
            if known_services:
                key = None
                for l in known_services.itervalues():
                    for service in l.itervalues():
                        if service['verif_orig'] == request.GET.get('orig', ''):
                            key = service['secret']
                if key is None or not signature.check_query(request.META['QUERY_STRING'], key):
                    raise PermissionDenied('signature is missing or wrong')
        data = []
        for visualization in self.get_queryset():
            path = reverse('visualization-iframe', kwargs={'pk': visualization.pk})
            sig = hashlib.sha1(path + settings.SECRET_KEY).hexdigest()
            path += '?signature=' + sig
            data_uri = reverse('visualization-json', kwargs={'pk': visualization.pk})
            data.append({
                'name': visualization.name,
                'path': request.build_absolute_uri(path),
                'data-url': request.build_absolute_uri(data_uri),
            })
        response = HttpResponse(content_type='application/json')
        response.write(json.dumps(data))
        return response


class CubeIframeView(CubeView):
    template_name = 'bijoe/cube_raw.html'


class VisualizationODSView(DetailView):
    model = models.Visualization

    def get(self, request, *args, **kwargs):
        instance = self.get_object()
        visualization = Visualization.from_json(instance.parameters, request=request)
        response = HttpResponse(content_type='application/vnd.oasis.opendocument.spreadsheet')
        response['Content-Disposition'] = 'attachment; filename=%s.ods' % slugify(instance.name)
        workbook = visualization.ods()
        workbook.save(response)
        return response


class VisualizationGeoJSONView(generics.GenericAPIView):
    permission_classes = ()
    queryset = models.Visualization.objects.all()

    def get(self, request, pk, format=None):
        instance = self.get_object()
        visualization = Visualization.from_json(instance.parameters, request=request)
        visualization.measure = visualization.cube.measures['geolocation']
        drilldown = visualization.drilldown
        geojson = []
        for row in visualization.data():
            properties = {}
            for dim in row[:len(drilldown)]:
                properties[dim['label']] = dim['value']
            geojson.append({
                'type': 'Feature',
                'geometry': {
                    'type': 'MultiPoint',
                    'coordinates': row[len(drilldown)]['value'] or [],
                },
                'properties': properties,
            })
        return Response(geojson)


class VisualizationJSONView(generics.GenericAPIView):
    permission_classes = ()
    queryset = models.Visualization.objects.all()

    def get(self, request, pk, format=None):
        instance = self.get_object()
        loop = []
        all_visualizations = Visualization.from_json(instance.parameters, request=request)
        for visualization in all_visualizations:
            drilldowns = visualization.drilldown
            if len(drilldowns) == 2:
                x_labels = [x.label for x in visualization.drilldown_x.members]
                y_labels = [y.label for y in visualization.drilldown_y.members]
                used_x_labels = OrderedDict()
                used_y_labels = OrderedDict()
                default = 0
                grid = {(x, y): default for x in x_labels for y in y_labels}

                for row in visualization.data():
                    x_label = unicode(row[0]['value'])
                    y_label = unicode(row[1]['value'])
                    used_x_labels[x_label] = True
                    used_y_labels[y_label] = True
                    grid[(x_label, y_label)] = row[2]['value']

                data = []
                for y in used_y_labels.keys():
                    data.append([grid[(x, y)] for x in used_x_labels.keys()])
                axis = {
                    'x_labels': [x.strip() for x in used_x_labels.keys()],
                    'y_labels': [x.strip() for x in used_y_labels.keys()]
                }
            elif len(drilldowns) == 1:
                table = list(visualization.data())
                axis_data = [x[0]['value'].strip() for x in table]
                data = [x[1]['value'] for x in table]
                if visualization.drilldown_x:
                    axis = {'x_labels': axis_data}
                else:
                    axis = {'y_labels': axis_data}
            elif len(drilldowns) == 0:
                data = list(list(visualization.data())[0])[0]['value']
                axis = {}
            loop.append({'data': data, 'axis': axis})

        if not all_visualizations.loop:
            data = loop[0]['data']
            axis = loop[0]['axis']
        else:
            axis = loop[0]['axis']
            axis['loop'] = [x.label for x in all_visualizations.loop.members]
            data = [x['data'] for x in loop]

        return Response({
            'data': data,
            'axis': axis,
            'format': '1',
            })


warehouse = WarehouseView.as_view()
cube = CubeView.as_view()
cube_iframe = xframe_options_exempt(CubeIframeView.as_view())
visualizations = VisualizationsView.as_view()
visualizations_json = VisualizationsJSONView.as_view()
create_visualization = CreateVisualizationView.as_view()
delete_visualization = DeleteVisualizationView.as_view()
rename_visualization = RenameVisualization.as_view()
visualization = VisualizationView.as_view()
visualization_iframe = xframe_options_exempt(VisualizationIframeView.as_view())
visualization_geojson = VisualizationGeoJSONView.as_view()
visualization_ods = VisualizationODSView.as_view()
visualization_json = VisualizationJSONView.as_view()

cube_iframe.mellon_no_passive = True
visualization_iframe.mellon_no_passive = True
