#Copyright ReportLab Europe Ltd. 2000-2012
#see license.txt for license details
# $Id$

# XXX this module shouldn't know about brochures

__all__ = ['customerStats']

import os, datetime
from cPickle import loads

from mx import DateTime

from django.http import HttpResponseNotAllowed
from django.shortcuts import render_to_response
from django import forms
from django.conf import settings

from reportlab.graphics import renderPM

from rldjango.common import utils
from rldjango.stats.logchart import LogChartDrawing
import rldjango.stats.logger

from rlextra.ers import docgendb

# IP addresses of places used by RL staff to test sites
RL_IP_ADDRESSES = ['217.196.247.135']


class RequestWrapper:

    def __init__(self, timestamp, url, requestTuple, bad=False):
        self.timestamp = timestamp
        self.url = url
        path, self.get, self.post, self.cookies, self.meta, self.files, self.userId = requestTuple
        #assert url == path
        self.bad = bad

def badRequest(timestamp, url):
    nullReqTup = tuple(['<bad request>'] + [{}]*5 + ['<bad request>'])
    return RequestWrapper(timestamp, url, nullReqTup, bad=True)


def requestListing(request):
    #from django.core.handlers.base import get_traceback
    nrDaysBack = 5
    sql = """\
SELECT h.timestamp, h.action, h.request
FROM HitLogEntry h
  WHERE DATE(h.timestamp) >= DATE_SUB(NOW(), INTERVAL '%s 0' DAY_HOUR)
  AND h.applicationName = %%s
ORDER BY h.timestamp DESC
""" % (nrDaysBack,)
    applicationName = settings.APPNAME
    args = (applicationName,)
    conn = rldjango.stats.logger.LOGGER.getConnection()
    rows = list(docgendb.HitLogEntry.query(conn, sql, args))
    requests = []
    for row in rows:
        try:
            request = RequestWrapper(row[0], row[1], loads(row[2].tostring()))
        except (SystemError, KeyboardInterrupt):
            raise
        except:
            # unpickling Django's MultiValueDict objects can cause AttributeError (Python bug)
            requests.append(badRequest(row[0], row[1]))
        else:
            requests.append(request)
    conn.commit()
    return render_to_response('stats/requests.html',
                              dict(title='Hit Log Request Listing',
                                   requests=requests,
                                   nrDaysBack=nrDaysBack,
                                   ),
                              )

class CustomerStatsManipulator(forms.Manipulator):
    def __init__(self, fields):
        formFields = [
            forms.SelectField(field_name="period", choices=[('week', 'Week'),
                                                            ('month', 'Month'),
                                                            ('quarter', 'Quarter'),
                                                            ('year', 'Year'),
                                                            ('fiveyears', '5 Years'),
                                                            ]),
        ]
        radioChoices = []
        for name, (displayName, categories) in fields.items():
            field = forms.SelectField(field_name=name,
                                      choices=[('all', '<All>'), ('null', '<None>')]+categories,
                                      )
            formFields.append(field)
            radioChoices.append((name, displayName))
        formFields.append(forms.RadioSelectField(field_name='plotField', choices=radioChoices))
        self.fields = tuple(formFields)


class DataFetcher:

    def __init__(self, fieldsDict, data):
        self._fields = fieldsDict
        self._plotField = data['plotField']

        # sets .plotCategoryId as a side-effect
        restrictions = {}
        for name, (displayName, categories) in self._fields.items():
            value = data[name]
            if value == 'null':
                value = None
            if name == self._plotField:
                self.plotCategoryId = value
            if value == 'all':
                continue
            restrictions[name] = value
        self._restrictions = restrictions

    def fieldNames(self):
        return [dict(name=name, displayName=displayName) for
                name, (displayName, junk) in self._fields.items()
                ]

    def getData(self, conn, startDate, serverName, excludedIpAddresses, usingBrochureLogEntry):
        plotField = self._plotField
        constraints = self._restrictions
        r = []
        seriesNames = []
        for category, seriesName in zip(self._plotCategories(), self._seriesNames()):
            constraints[plotField] = category
            hits = getHits(conn, constraints, startDate, serverName, excludedIpAddresses,
                           usingBrochureLogEntry)
            if hits is not None:
                r.append(hits)
                seriesNames.append(seriesName)
        conn.commit()
        return r, seriesNames

    def _plotCategories(self):
        plotCategoryId = self.plotCategoryId
        plotField = self._plotField
        if plotCategoryId == 'all':
            return [None]+[name for name, displayName in self._fields[plotField][1]]
        else:
            return [plotCategoryId]

    def _seriesNames(self):
        plotCategoryId = self.plotCategoryId
        plotField = self._plotField
        if plotCategoryId == 'all':
            return ['None']+[displayName for name, displayName in self._fields[plotField][1]]
        else:
            if plotCategoryId is None:
                return ['None']
            else:
                return [dict(self._fields[plotField][1])[plotCategoryId]]


def addZeroHitRows(rows, startDate):
    """Add zero rows to [(date, nrHits), ...] daily usage statistics.

    Simpler than an outer join.
    """
    # utility function for use by users of customerUsageStats()
    date = startDate
    day = datetime.timedelta(1)
    today = datetime.date.today()
    hits = []
    ii = 0
    try:
        statsDate, nrHits = rows.pop()
    except IndexError:
        statsDate, nrHits = date, 0
    while date <= today:
        if statsDate == date:
            hits.append(nrHits)
            try:
                statsDate, nrHits = rows.pop()
            except IndexError:
                break
        else:
            hits.append(0)
        date += day
    else:
        return hits
    padding = (today-startDate).days - len(hits) + 1
    return hits + [0]*padding

def getHits(conn, constraints, startDate, serverName, excludedIpAddresses,
            usingBrochureLogEntry):
    # Retrieve list of number of hits per day, one item per day. We fill in
    # the missing rows in Python code rather than in SQL to avoid outer join
    # and subselect.
    whereClauses = []
    whereArgs = []
    for fieldName, category in constraints.items():
        if category is None:
            whereClauses.append('AND %s is NULL' % fieldName)
        else:
            whereClauses.append('AND %s = %%s' % fieldName)
            whereArgs.append(category)
    if 1:
        whereClauses.extend([
            'AND h.serverName = %s',
            'AND h.testUser IS NULL',
            ])
        ipAddressMarkers = "(%s)" % (", ".join(["%s"]*len(excludedIpAddresses)))
        whereClauses.append('AND NOT h.remoteIpAddress IN %s' % ipAddressMarkers)
        whereArgs.extend([serverName]+excludedIpAddresses)
    if usingBrochureLogEntry:
        whereClauses.append('AND d.id = b.docLogEntryId')
    if whereClauses:
        whereText = '\n  '.join(whereClauses)
    else:
        whereText = ''

    tablesText = 'HitLogEntry h, DocumentLogEntry d'
    if usingBrochureLogEntry:
        tablesText += ', BrochureLogEntry b'

    sql = """\
SELECT DATE(h.timestamp), COUNT(h.id) AS hits
FROM %s
  WHERE h.id = d.hitLogEntryId
  AND DATE(h.timestamp) >= %%s
  AND h.applicationName = %%s
  %s
GROUP BY DATE(h.timestamp)
ORDER BY DATE(h.timestamp) DESC
""" % (tablesText, whereText,)
    startDate = datetime.date(startDate.year, startDate.month, startDate.day)
    applicationName = settings.APPNAME
    args = tuple([startDate, applicationName]+whereArgs)
    rows = list(docgendb.HitLogEntry.query(conn, sql, args))

    if not rows:
        return None
    return addZeroHitRows(rows, startDate)


def setDefaultsFromManipulator(data, manipulator):
    for field in manipulator.fields:
        if data[field.field_name] is None:
            data[field.field_name] = field.choices[0][0]


def customerUsageStats(request,
                       fields,
                       excludedIpAddresses=RL_IP_ADDRESSES,
                       projectComment='',
                       usingBrochureLogEntry=True,
                       ):
    """fields is a dictionary specifying the usage database fields that
    should be displayed in the usage view:

    XXX this currently randomizes field (and field value?) orderings -- should
    get rid of all use of dicts in both"fields" argument and the implementation.

    fields = {
        'd.documentCode': ('Type of Document',
                      [('ebrochure', 'eBrochure'), ('quote', 'Quote')]
                      ),
        }

    The keys should be database field names, with prefixes 'b.', 'd.', and 'h.'
    standing for BrochureLogEntry, DocumentLogEntry, and HitLogEntry fields
    respectively.

    The values should be a 2-tuple, (fieldDisplayName, choices), where
    fieldDisplayName is the name to display in the view for this database
    field, and choices is the list of choices (often this will come from the
    database).

    usingBrochureLogEntry should be true if the BrochureLogEntry table is in
    use (if true, an extra join is added to the SQL query used to fetch the
    usage records).  XXX Really this module shouldn't know about travel
    Brochures...

    """

    from django.conf import settings

    if request.method != 'GET':
        return HttpResponseNotAllowed(['GET'])

    manipulator = CustomerStatsManipulator(fields)

    new_data = request.GET.copy()
    errors = manipulator.get_validation_errors(new_data)
    if not errors:
        manipulator.do_html2python(new_data)
    else:
        assert False

    form = forms.FormWrapper(manipulator, new_data, errors)
    setDefaultsFromManipulator(new_data, manipulator)
    dataFetcher = DataFetcher(fields, new_data)
    periodId = new_data['period']

    drawing = LogChartDrawing()
    chart = drawing.chart

    nrDays = dict(
        week=7,
        month=30,
        quarter=91,
        year=365,
        fiveyears=1826,
        )[periodId]
    deltaDays = dict(
        week=1,
        month=1,
        quarter=7,
        year=30,
        fiveyears=30,
        )[periodId]
    nrTicks = dict(
        week=7,
        month=5,
        quarter=6,
        year=5,
        fiveyears=10,
        )[periodId]
    nrPoints = nrDays/deltaDays

    startDate = DateTime.now()-DateTime.DateTimeDelta(nrDays-deltaDays)
    serverName = request.META.get('SERVER_NAME')
    getConnection = rldjango.stats.logger.LOGGER.getConnection

    data, seriesNames = dataFetcher.getData(getConnection(), startDate, serverName, excludedIpAddresses,
                                            usingBrochureLogEntry)
    if not len(seriesNames):
        url = dates = totals = None
    else:
        # XXX should take an average here, rather than just throwing away data (in
        # fact, moving average might not be a bad idea) note we currently use the
        # same data to draw the tables in this view, though
        data = [hits[::deltaDays] for hits in data]
        totals = [sum(hits) for hits in data]
        chart.data = data
        chart.seriesNames = seriesNames

        dates = []
        delta = DateTime.DateTimeDelta(deltaDays)
        date = startDate
        while date <= DateTime.now():
            dates.append(date)
            date += delta

        dateFormat = dict(
            week='%d %b',
            month='%d %b',
            quarter='%d %b %Y',
            year='%b %Y',
            fiveyears='%b %Y',
            )[periodId]
        tickEvery = nrPoints / nrTicks
        plotCategoryNames = []
        for ii, date in enumerate(dates):
            if ii%tickEvery == 0:
                plotCategoryNames.append(date.strftime(dateFormat))
            else:
                plotCategoryNames.append('')
        chart.categoryNames = plotCategoryNames

        fn, url = utils.fullUniqueFilenameAndUrlPath(ext='.gif')
        renderPM.drawToFile(drawing, fn)
    
    return render_to_response('stats/customer_usage.html',
                              dict(title='eBrochures Usage Log Summary',
                                   form=form,
                                   chartUrl=url,
                                   datas=data,
                                   totals=totals,
                                   dates=dates,
                                   seriesNames=seriesNames,
                                   fieldNames=dataFetcher.fieldNames(),
                                   projectComment=projectComment,
                                   user = getattr(request, 'user', ''),
                                   ),
                              )
