# w.c.s. - web application for online forms
# Copyright (C) 2005-2010  Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301  USA

import random
from quixote.html import htmltext, TemplateIO
from qommon.form import *
from wcs.fields import WidgetField, register_field_class

class RankedItemsWidget(CompositeWidget):
    readonly = False

    def __init__(self, name, value = None, elements = None, **kwargs):
        CompositeWidget.__init__(self, name, value, **kwargs)
        self.element_names = {}

        if kwargs.has_key('title'):
            del kwargs['title']
        if kwargs.has_key('readonly'):
            if kwargs['readonly']:
                self.readonly = True
            del kwargs['readonly']
        if kwargs.has_key('required'):
            if kwargs['required']:
                self.required = True
            del kwargs['required']

        self.randomize_items = False
        if kwargs.has_key('randomize_items'):
            if kwargs['randomize_items']:
                self.randomize_items = True
            del kwargs['randomize_items']

        for v in elements:
            if type(v) is tuple:
                title = v[1]
                key = v[0]
                if type(key) is int:
                    name = 'element%d' % v[0]
                elif type(key) in (str, htmltext):
                    name = str('element%s' % v[0])
                    key = str(key)
                else:
                    raise NotImplementedError()
            else:
                title = v
                key = v
                name = 'element%d' % len(self.element_names.keys())

            if value:
                position = value.get(key)
            else:
                position = None
            self.add(IntWidget, name, title = title, value = position, size = 5, **kwargs)
            self.element_names[name] = key

        if self.randomize_items:
            random.shuffle(self.widgets)

        if self.readonly:
            def cmp_w(x, y):
                if x.value is None and y.value is None:
                    return 0
                if x.value is None:
                    return 1
                if y.value is None:
                    return -1
                return cmp(x.value, y.value)
            self.widgets.sort(cmp_w)


    def _parse(self, request):
        values = {}
        for name in self.element_names:
            value = self.get(name)
            values[self.element_names[name]] = value
            if type(value) is not int:
                self.get_widget(name).set_error(IntWidget.TYPE_ERROR)
        self.value = values or None

    def parse(self, request=None):
        value = CompositeWidget.parse(self, request=request)
        for widget in self.widgets:
            if widget.has_error():
                self.set_error(_('Some fields were not filled properly.'))
                break
        return value

    def render_content(self):
        r = TemplateIO(html=True)
        r += htmltext('<ul>')
        for widget in self.get_widgets():
            if widget.has_error():
                r += htmltext('<li class="error"><label>')
            else:
                r += htmltext('<li><label>')
            if self.readonly:
                widget.attrs['disabled'] = 'disabled'
                if widget.value:
                    r += htmltext('<input type="hidden" name="%s" value="%s" >') % (
                            widget.name, widget.value)
                widget.name = widget.name + 'xx'
            r += widget.render_content()
            r += htmltext('</label>')
            r += widget.title
            r += htmltext('</li>')
        r += htmltext('</ul>')
        return r.getvalue()


class RankedItemsField(WidgetField):
    key = 'ranked-items'
    description = N_('Ranked Items')

    items = None
    randomize_items = True
    widget_class = RankedItemsWidget

    def perform_more_widget_changes(self, form, kwargs, edit = True):
        kwargs['elements'] = self.items or []
        kwargs['randomize_items'] = self.randomize_items

    def fill_admin_form(self, form):
        WidgetField.fill_admin_form(self, form)
        form.add(WidgetList, 'items', title = _('Items'), element_type = StringWidget,
                value = self.items, required = True,
                element_kwargs = {'render_br': False, 'size': 50},
                add_element_label = _('Add item'))
        form.add(CheckboxWidget, 'randomize_items', title = _('Randomize Items'),
                value = self.randomize_items)

    def get_admin_attributes(self):
        return WidgetField.get_admin_attributes(self) + ['items', 'randomize_items']

    def get_view_value(self, value):
        r = TemplateIO(html=True)
        r += htmltext('<ul>')
        items = value.items()
        items.sort(lambda x,y: cmp(x[1], y[1]))
        for it in items:
            if it[1]:
                r += htmltext('<li>%s: %s</li>') % (it[1], it[0])
        r += htmltext('</ul>')
        return r.getvalue()

    def stats(self, values):
        r = TemplateIO(html = True)

        # hardcoded to condorcet for the moment
        candidates = self.items
        # compute matrix
        matrix = {}
        for c1 in candidates:
            matrix[c1] = {}
            for c2 in candidates:
                matrix[c1][c2] = 0
        votes = [x.data.get(self.id) for x in values]
        votes = [x for x in votes if x]
        for vote in votes:
            for c1 in candidates:
                for c2 in candidates:
                    if c1 == c2:
                        continue
                    vote_a = vote.get(c1)
                    vote_b = vote.get(c2)
                    if vote_a is None:
                        vote_a = 99999 # XXX MAX_INT
                    if vote_b is None:
                        vote_b = 99999 # idem
                    if int(vote_a) == int(vote_b):
                        matrix[c1][c2] += 0.5
                    elif int(vote_a) < int(vote_b):
                        matrix[c1][c2] += 1
        import pprint
        pprint.pprint(matrix)
        # compute ratings
        ratings = {}
        for c1 in candidates:
            ratings[c1] = {'win': [], 'loss': [], 'tie': [], 'worst': 0}
            for c2 in candidates:
                if c1 == c2:
                    continue
                delta = matrix[c1][c2] - matrix[c2][c1]
                if delta > 0:
                    ratings[c1]['win'].append(c2)
                elif delta < 0:
                    ratings[c1]['loss'].append(c2)
                    if delta < ratings[c1]['worst']:
                        ratings[c1]['worst'] = -delta
                else:
                    ratings[c1]['tie'].append(c2)

        pprint.pprint(ratings)

        # compute winner
        winners = []
        remaining = candidates[:]
        for c1 in remaining:
            rating = ratings[c1]
            winner = True
            for loss in rating['loss']:
                if loss not in winners:
                    winner = False
                    break
            if not winner:
                continue
            for tie in rating['tie']:
                if tie not in winners:
                    winner = False
                    break
            if not winner:
                continue
            winners.append(c1)
            remaining.remove(c1)
            break
        else:
            narrowest = None
            winners = []
            for c2 in remaining:
                rating = ratings[c2]
                if narrowest is None or rating['worst'] < narrowest:
                    narrowest = rating['worst']
                    winners = [c2]
                elif rating['worst'] == narrowest:
                    winners.append(c2)

        candidates.sort(lambda x,y: cmp(len(ratings[x]['win']), len(ratings[y]['win'])))

        r += htmltext('<table>'
                '<thead>'
                '<tr>'
                '<td></td>')
        for c in candidates:
            r += htmltext('<th>%s</th>' % c)
        r += htmltext('</tr>'
                '</thead>'
                '<tbody>')
        for c1 in candidates:
            r += htmltext('<tr>'
                    '<th>%s</th>' % c1)
            for c2 in candidates:
                if c2 == c1:
                    r += htmltext('<td></td>')
                else:
                    if matrix[c1][c2] > matrix[c2][c1]:
                        color = '#0f0'
                    elif matrix[c1][c2] == matrix[c2][c1]:
                        color = '#ff0'
                    else:
                        color = '#f00'
                    r += htmltext('<td style="background: %s">' % color)
                    r += '%.1f' % matrix[c1][c2]
                    r += htmltext('</td>')
            r += htmltext('</tr>')
        r += htmltext('</tbody>'
                '</table>'
                '<p>')
        r += _('Winner:')
        r += ' '
        r += ' '.join(winners)
        r += htmltext('</p>')
        return r.getvalue()

register_field_class(RankedItemsField)



