# 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 wcs.qommon.form import CompositeWidget, IntWidget, WidgetList, StringWidget, \
        CheckboxWidget, SingleSelectWidget
from wcs.fields import WidgetField, register_field_class
from pyvotecore import schulze_method, irv, ranked_pairs, schulze_pr, \
        schulze_stv, schulze_npr

class VoteWidget(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()

METHODS = {
        'condorcet-schulze': N_('Condorcet-Shulze'),
        'irv': N_('Instant Run-off'),
        'ranked_pairs': N_('Ranked pairs'),
        'schulze-pr': N_('Schulze Proportional Ranking'),
        'schulze-stv': N_('Schulze STV'),
        'schulze-npr': N_('Schulze NPR'),
        }

class VoteField(WidgetField):
    key = 'vote-field'
    description = N_('Ranked choice vote')

    items = None
    randomize_items = True
    widget_class = VoteWidget
    required_winners = None
    winner_threshold = None
    tallying_method = 'condorcet-schulze'

    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)
        form.add(SingleSelectWidget, 'tallying_method', title=_('Tallying method'),
                value=self.tallying_method,
                options=METHODS.items())
        form.add(IntWidget, 'required_winners', title=_('Required winners'),
                value=self.required_winners),
        form.add(IntWidget, 'winner_threshold', title=_('Winner threshold'),
                value=self.winner_threshold),

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

    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):
        '''
           Compute vote result using the pyvotecore library.
        '''
        r = TemplateIO(html = True)

        votes = [x.data.get(self.id) for x in values]
        # build ballots
        kwargs = { 'winner_threshold': self.winner_threshold,
                'required_winners': self.required_winners }
        if self.tallying_method == 'condorcet-schulze':
            votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
            method = schulze_method.SchulzeMethod
        elif self.tallying_method == 'irv':
            votes = [ x.items() for x in votes ]
            votes = [ sorted(x, cmp=lambda x,y: cmp(x[1],y[1])) for x in votes ]
            votes = [ {'count':1, 'ballot': [ a for a,b in x ] } for x in votes ]

            method = irv.IRV
        elif self.tallying_method == 'ranked_pairs':
            votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
            method = ranked_pairs.RankedPairs
        elif self.tallying_method == 'schulze-pr':
            votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
            method = schulze_pr.SchulzePR
        elif self.tallying_method == 'schulze-stv':
            votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
            method = schulze_stv.SchulzeSTV
        elif self.tallying_method == 'schulze-npr':
            votes = [{ 'count': 1, 'ballot': x } for x in votes if x]
            method = schulze_npr.SchulzeNPR
        else:
            raise ValueError, 'unknown method', self.tallying_method
        # Restrain arguments
        accepted_args = method.__init__.im_func.func_code.co_varnames
        for key in kwargs.keys():
            if key not in accepted_args or kwargs.get(key) is None:
                del kwargs[key]
        # Run vote
        result = method(votes, **kwargs).as_dict()

        r += htmltext('<h4>%s</h4>') % _('Method')
        r += htmltext('<p>%s</p>') % _(METHODS.get(self.tallying_method))
        if 'candidates' in result:
            r += htmltext('<h4>%s</h4>') % _('Candidates')
            r += htmltext('<p>%s</p>') % (', '.join(result['candidates']))
        if 'quota' in result:
            r += htmltext('<h4>%s</h4>') % _('Quota')
            r += htmltext('<p>%s</p>') % result.get('quota')
        if 'rounds' in result:
            for i, _round in enumerate(result['rounds']):
                r += htmltext('<h4>%s</h4><div class="round">') % (_('Round %s') % (i+1))
                if 'loser' in _round:
                    r += htmltext('<h5>%s</h5>') % _('Loser')
                    r += htmltext('<p>%s</p>') % _round['loser']
                if 'tallies' in _round:
                    r += htmltext('<h5>%s</h5><ul>') % _('Tallies')
                    for a, b in _round['tallies'].iteritems():
                        r += htmltext('<li>%s: %s</li>') % (a,b)
                    r += htmltext('</ul>')
                if 'tied_losers' in _round:
                    r += htmltext('<h5>%s</h5>') % _('Tied losers')
                    r += htmltext('<p>%s</p>') % ', '.join(list(_round['tied_losers']))
                if 'winner' in _round:
                    r += htmltext('<h5>%s</h5>') % _('Winner')
                    r += htmltext('<p>%s</p>') % _round['winner']
                r += htmltext('</div>')
        if 'pairs' in result:
            r += htmltext('<h4>%s</h4><dl>') % _('Pairs')
            for a, b in result['pairs'].iteritems():
                r += htmltext('<dt>%s</dt>') % ', '.join(a)
                r += htmltext('<dd>%s</dd>') % b
            r += htmltext('</dl>')
        if 'strong_pairs' in result:
            r += htmltext('<h4>%s</h4><dl>') % _('Strong pairs')
            for a, b in result['strong_pairs'].iteritems():
                r += htmltext('<dt>%s</dt>') % ', '.join(a)
                r += htmltext('<dd>%s</dd>') % b
            r += htmltext('</dl>')

        if 'winner' in result:
            r += htmltext('<h4>%s</h4>') % _('Winner')
            r += htmltext('<p>%s</p>') % result['winner']
        if 'winners' in result:
            r += htmltext('<h4>%s</h4>') % _('Winners')
            r += htmltext('<p>%s</p>') % ', '.join(result['winners'])
        if 'order' in result:
            r += htmltext('<h4>%s</h4>') % _('Order')
            r += htmltext('<p>%s</p>') % ', '.join(result['order'])
        # FIXME: show actions
        # import pprint
        # r += htmltext('<pre>%s</pre>') % pprint.pformat(result)
        return r.getvalue()

register_field_class(VoteField)
