# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


__doc__ = """Glasnost Votes Common Models"""

__version__ = '$Revision$'[11:-2]


from ObjectsCommon import AdminWithoutWritersCommon, ObjectCommon, ObjectsCommonMixin


class AdminVotesCommon(AdminWithoutWritersCommon):
    serverRole = 'votes'


class AbstractVoteCommon(ObjectCommon):
    ballotKind = None
    ballotKind_kind_isRequired = 1
    ballotKind_kind_values = [
        'public',
        'secret',
        ]
    ballotKind_kindName = 'Choice'

    comment = None
    comment_kindName = 'String'

    electionToken = None
    electionToken_kindName = 'Token'

    marks = None
    marks_kind_isRequired = 1
    marks_kindName = 'Marks'

    serverRole = 'votes'

    token = None
    token_kindName = 'Token'

    voterId_kind_importExport = 'private'
    voterId_kind_serverRoles = ['identities']
    voterId_kindName = 'Id'

    voterToken = None
    voterToken_kindName = 'Token'

    def canCache(self):
        return 0

    def getDistribution(self, candidateIds):
        rating = self.getRating(candidateIds)
        if len(rating) == 0:
            return {}
        distribution = {}
        ratesSum = 0
        for rate in rating.values():
            ratesSum += rate
        if ratesSum == 0:
            score = 1 / float(len(rating))
            for candidateId in rating.keys():
                distribution[candidateId] = score
        else:
            coef = 1 / float(ratesSum)
            for candidateId, rate in rating.items():
                distribution[candidateId] = rate * coef
        return distribution

    def getLabel(self):
        label = self.getName()
        if label is None:
            return ''
        return label

    def getLabelLanguage(self):
        return ''

    def getMarks(self, candidateIds):
        return self.repairMarks(self.marks, candidateIds)

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = ObjectCommon.getOrderedLayoutSlotNames(
            self, parentSlot = parentSlot)
        slotNames += [
            'voterId', 'voterToken', 'electionId', 'subject', 'token',
            'ballotKind', 'marks', 'comment']
        return slotNames

    def getRanking(self, candidateIds):
        rating = self.getRating(candidateIds)
        if len(rating) == 0:
            return {}
        candidateIds = rating.keys()[:]
        def candidatesSorter(id1, id2, rating = rating):
            return cmp(rating[id2], rating[id1])
        candidateIds.sort(candidatesSorter)
        ranking = {}
        if len(candidateIds) > 0:
            rank = 1
            previousRate = rating[candidateIds[0]]
            for candidateId in candidateIds:
                rate = rating[candidateId]
                if previousRate < rate:
                    rank += 1
                    previousRate = rate
                ranking[candidateId] = rank
        return ranking

    def getRating(self, candidateIds):
        distribution = self.getDistribution(candidateIds)
        if len(distribution) == 0:
            return {}
        rating = {}
        minValue = min(distribution.values())
        maxValue = max(distribution.values())
        if minValue == maxValue:
            for candidateId in distribution.keys():
                rating[candidateId] = 0
        else:
            coef = 1 / float(maxValue - minValue)
            for candidateId, value in distribution.items():
                rating[candidateId] = (value - minValue) * coef
        return rating

    def getName(self):
        if self.token:
            return 'Vote %s' % self.token
        return 'Vote'

    def isAbstention(self):
        return self.marks is None


class VoteDistributionCommon(AbstractVoteCommon):
    def getDistribution(self, candidateIds):
        return self.repairMarks(self.marks, candidateIds)

    def isBlank(self, candidateIds):
        if self.isAbstention():
            return 0
        marks = self.repairMarks(self.marks, candidateIds)
        minMark = min(marks.values())
        maxMark = max(marks.values())
        return minMark == maxMark == 0

    def repairMarks(self, marks, candidateIds):
        if marks is None:
            return {}
        distribution = {}
        marksSum = 0
        for candidateId, mark in marks.items():
            if not candidateId in candidateIds:
                continue
            marksSum += mark
        if marksSum == 0:
            # Blank vote.
            for candidateId in candidateIds:
                distribution[candidateId] = 0
        else:
            coef = 1 / float(marksSum)
            for candidateId in candidateIds:
                if marks.has_key(candidateId):
                    value = marks[candidateId] * coef
                else:
                    value = 0
                distribution[candidateId] = value
        return distribution


class VoteRankingCommon(AbstractVoteCommon):
    def getRanking(self, candidateIds):
        return self.repairMarks(self.marks, candidateIds)

    def getRating(self, candidateIds):
        ranking = self.getRanking(candidateIds)
        if len(ranking) == 0:
            return {}
        rating = {}
        lastRank = max(ranking.values())
        newRank = 1
        for rank in range(1, lastRank + 1):
            tieIds = filter(lambda id, ranking = ranking, rank = rank:
                            ranking[id] == rank,
                            ranking.keys())
            rate = - (2 * newRank + len(tieIds) - 1) / 2.0
            for candidateId in tieIds:
                rating[candidateId] = rate
            newRank = newRank + len(tieIds)
        minRate = min(rating.values())
        maxRate = max(rating.values())
        if minRate == maxRate:
            for candidateId in rating.keys():
                rating[candidateId] = 0
        else:
            coef = 1 / float(maxRate - minRate)
            for candidateId, rate in rating.items():
                rating[candidateId] = (rate - minRate) * coef
        return rating

    def getScores(self, candidateIds):
        ranking = self.getRanking(candidateIds)
        if len(ranking) == 0:
            return {}
        scores = {}
        lastRank = max(ranking.values())
        newRank = 1
        for rank in range(1, lastRank + 1):
            tieIds = filter(lambda id, ranking = ranking, rank = rank:
                            ranking[id] == rank,
                            ranking.keys())
            score = - (2 * newRank + len(tieIds) - 1) / 2.0
            for candidateId in tieIds:
                scores[candidateId] = score
            newRank = newRank + len(tieIds)
        minScore = - len(candidateIds)
        maxScore = -1
        if minScore == maxScore:
            score = 1 / float(len(candidateIds))
            for candidateId in scores.keys():
                scores[candidateId] = score
        else:
            coef = 1 / float(maxScore - minScore)
            for candidateId, score in scores.items():
                scores[candidateId] = (score - minScore) * coef
        return scores

    def isBlank(self, candidateIds):
        if self.isAbstention():
            return 0
        marks = self.repairMarks(self.marks, candidateIds)
        foundMarks = []
        for mark in marks.values():
            if not mark in foundMarks:
                foundMarks.append(mark)
        return len(foundMarks) == 1 and foundMarks[0] is None

    def repairMarks(self, marks, candidateIds):
        if marks is None:
            return {}
        marks = marks.copy()
        for candidateId in marks.keys():
            if not candidateId in candidateIds:
                del marks[candidateId]
        for candidateId in candidateIds:
            if not marks.has_key(candidateId):
                marks[candidateId] = None
        def candidatesSorter(id1, id2, marks = marks):
            mark1 = marks[id1]
            mark2 = marks[id2]
            if mark1 == mark2:
                return 0
            elif mark1 is None:
                return 1
            elif mark2 is None:
                return -1
            else:
                return cmp(mark1, mark2)
        candidateIds = candidateIds[:]
        candidateIds.sort(candidatesSorter)
        ranking = {}
        if len(candidateIds) > 0:
            previousMark = marks[candidateIds[0]]
            if previousMark is None:
                for candidateId in candidateIds:
                    ranking[candidateId] = None
            else:
                rank = 1
                for candidateId in candidateIds:
                    mark = marks[candidateId]
                    if previousMark is not None \
                       and (mark is None or previousMark < mark):
                        rank += 1
                        previousMark = mark
                    ranking[candidateId] = rank
        return ranking


class VoteRatingCommon(AbstractVoteCommon):
    def getRating(self, candidateIds):
        return self.repairMarks(self.marks, candidateIds)

    def isBlank(self, candidateIds):
        if self.isAbstention():
            return 0
        marks = self.repairMarks(self.marks, candidateIds)
        minMark = min(marks.values())
        maxMark = max(marks.values())
        return minMark == maxMark == 0

    def repairMarks(self, marks, candidateIds):
        if marks is None:
            return {}
        rating = {}
        minMark = min(marks.values())
        maxMark = max(marks.values())
        if minMark == maxMark == 0:
            for candidateId in candidateIds:
                rating[candidateId] = 0
        elif minMark == maxMark:
            for candidateId in candidateIds:
                rating[candidateId] = 1
        else:
            coef = 1 / float(maxMark - minMark)
            for candidateId in candidateIds:
                if marks.has_key(candidateId):
                    rate = (marks[candidateId] - minMark) * coef
                else:
                    rate = 0
                rating[candidateId] = rate
        return rating


class VoteApprovalCommon(VoteRatingCommon):
    def repairMarks(self, marks, candidateIds):
        if marks is None:
            return {}
        rating = {}
        for candidateId in candidateIds:
            if marks.has_key(candidateId):
                rate = marks[candidateId]
            else:
                rate = 0
            rating[candidateId] = rate
        return rating


class VotesCommonMixin(ObjectsCommonMixin):
    adminClassName = 'AdminVotes'
    newObjectNameCapitalized = N_('New Vote')
    objectClassName = 'AbstractVote'
    objectName = N_('vote')
    objectNameCapitalized = N_('Vote')
    objectsName = N_('votes')
    objectsNameCapitalized = N_('Votes')
    serverRole = 'votes'

