from common.models import Job, DeletedJob
from django.http import HttpResponse, HttpResponseNotFound, Http404, HttpResponseServerError, \
    HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
import logging
import re
import json
from collections import OrderedDict


class Api(View):
    http_method_names = ['get', 'post']

    @classmethod
    def as_view(cls, *args, **kwargs):
        return csrf_exempt(super(Api, cls).as_view(*args, **kwargs))

    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        fmt = args.pop()

        try:
            r = super(Api, self).dispatch(request, *args, **kwargs)
        except Http404, e:
            return HttpResponseNotFound(e)
        except Http400, e:
            return HttpResponse(
                content=e,
                status=400,
            )
        except Http423, e:
            return HttpResponse(
                content=e,
                status=423,
            )
        except Http403, e:
            return HttpResponseForbidden(e)
        except Exception, e:
            logging.exception("API ERROR in %s:", request.path)
            return HttpResponseServerError(e)
        coords = self.request.GET.get('coords', '')
        if coords:
            m = re.match(r'x:([^,]*),y:([^,]*),z:([^,]*)', coords)
            if m is not None:
                (x, y, z) = m.group(1, 2, 3)
                r = self.matrix(r, x, y, z)
        if isinstance(r, HttpResponse):
            return r
        if fmt == 'json':
            response = HttpResponse(json.dumps(r), content_type='application/json')
        elif fmt == 'csv':
            response = HttpResponse(self.render_csv(r), content_type='text/csv')
        elif fmt == 'csvs':
            response = HttpResponse(self.render_csvs(r), content_type='text/csv')
        else:
            response = HttpResponse(self.render_xml(r), content_type='application/xml')
        return response

    @staticmethod
    def render_xml(data):
        """
        Render data to XML format

        >>> Api().render_xml([
        ...    {'key1': 'value11', 'key2': 12},
        ...    {'key1': '<value21>', 'key2': 'value & 22'},])
        ["<?xml version='1.0' encoding='UTF-8'?>\\n", '<response>\\n', '    <result>\\n        <key2>12</key2>\\n
         <key1>value11</key1>\\n    </result>\\n', '    <result>\\n        <key2>value &amp; 22</key2>\\n
         <key1>&lt;value21&gt;</key1>\\n    </result>\\n', '</response>']
        """
        if not data:
            return "<?xml version='1.0' encoding='UTF-8'?>\n<response></response>\n"
        assert hasattr(data[0], 'keys')
        escaper = lambda x: x.replace('&', '&amp;'). \
            replace('>', '&gt;'). \
            replace('<', '&lt;')
        r = ["<?xml version='1.0' encoding='UTF-8'?>\n", '<response>\n']
        for item in data:
            line = ['    <result>']
            for k in item:
                line.append('        <%s>%s</%s>' % (k, escaper(unicode(item[k])), k))
            line.append('    </result>\n')
            r.append('\n'.join(line))
        r.append('</response>')
        return r

    @staticmethod
    def _render_csv(data, escaper):
        assert hasattr(data[0], 'keys')
        r = [';'.join(data[0].keys()), '\r\n']
        for item in data:
            line = []
            for k in item:
                value = escaper(unicode(item[k]))
                if (value.find(';') != -1
                    or value.find(',') != -1
                    or value.find(' ') != -1
                    or value.find('-') != -1  # Yandex JIRA CSV formatting
                    or value.find('\n') != -1):
                    line.append('"%s"' % value.replace('"', '""'))
                else:
                    line.append(value)
            r.append(';'.join(line))
            r.append('\r\n')
        r.pop()
        return r

    def render_csv(self, data):
        """
        Render data to CSV format: https://ru.wikipedia.org/wiki/CSV
        Data must be list (tuple) of dicts

        """
        if not data:
            return ""
        return self._render_csv(data, lambda x: x)

    def render_csvs(self, data):
        """
        Render data to simple CSV format - CSV without line breaks in values

        """
        if not data:
            return ""
        return self._render_csv(
            data,
            lambda x: x.replace('\r', '').replace('\n', ' ')
        )

    @staticmethod
    def matrix(r, x, y, z):
        row0 = r[0]
        r_cols = row0.keys()
        if x in r_cols and y in r_cols and z in r_cols:
            data = {}
            y_cols = {}
            x_rows = {}
            for row in r:
                y_cols[unicode(row[y])] = 1
                x_rows[unicode(row[x])] = 1
                if unicode(row[x]) not in data: data[unicode(row[x])] = {}
                data[unicode(row[x])][unicode(row[y])] = row[z]
            x_rows = x_rows.keys()
            try:
                x_rows.sort(key=int)
            except:
                x_rows.sort()
            y_cols = y_cols.keys()
            try:
                y_cols.sort(key=int)
            except:
                y_cols.sort()
            # print y_cols
            for data_row in data.values():
                for y_col in y_cols:
                    if y_col not in data_row: data_row[unicode(y_col)] = 0
            res = []
            for x_row in x_rows:
                res_row = [(x, x_row)]
                for y_col in y_cols:
                    res_row.append((y_col, data[x_row][unicode(y_col)]))
                res.append(OrderedDict(res_row))
            return res
        if y in r_cols and z in r_cols and not x:
            res_row = []
            for row in r:
                res_row.append((row[y], row[z]))
            return [OrderedDict(res_row)]

        return r


class ApiJob(Api):
    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        try:
            job = Job.check_job(Job.objects.get(n=args[0]))
        except Job.Deleted:
            return HttpResponse(json.dumps([{'success': 0, 'msg': 'Job had been deleted'}]), content_type='application/json')
        except Job.DoesNotExist:
            HttpResponseNotFound('Job not found')
        args[0] = job
        return super(ApiJob, self).dispatch(request, *args, **kwargs)


class ApiTask(Api):
    def dispatch(self, request, *args, **kwargs):
        args = list(args)
        if args[0] is None:
            return HttpResponseNotFound('Task not specified')
        args[0] = 1
        return super(ApiTask, self).dispatch(request, *args, **kwargs)


class Http400(Exception):
    pass


class Http423(Exception):
    pass


class Http403(Exception):
    pass
