#! /usr/bin/python

import re
import sys
import string
import md5

class Ballot:
    def __init__ (self):
        self.email = 0
        self.member = 0
        self.token = 0
        self.votes = []

    def add_vote (self, name, id):
        self.votes.append ((name, id))

class Candidate:
    def __init__ (self, name, id):
        self.name = name
        self.id = id
        self.count = 0
	self.voters = []
        
candidates = {}

candidate_tuples = [ \
    ("MARTIN SEVIOR", 1), \
    ("RICHARD STALLMAN", 2), \
    ("MIGUEL DE ICAZA", 3), \
    ("BILL HANEMAN", 4), \
    ("MIKE NEWMAN", 5), \
    ("GLYNN FOSTER", 6), \
    ("BASTIEN NOCERA", 7), \
    ("MALCOLM TREDINNICK", 8), \
    ("MICHAEL MEEKS", 9), \
    ("DANIEL VEILLARD", 10), \
    ("JAMES HENSTRIDGE", 11), \
    ("JEFF WAUGH", 12), \
    ("JIM GETTYS", 13), \
    ("LESLIE PROCTOR", 14), \
    ("NAT FRIEDMAN", 15), \
    ("JONATHAN BLANDFORD", 16), \
    ("JODY GOLDBERG", 17), \
    ("TIM NEY", 18), \
    ("LUIS VILLA", 19), \
    ("AMY KAHN", 20), \
    ("MARTIN BAULIG", 21), \
    ("SRI RAMKRISHNA", 22), \
    ("FEDERICO MENA QUINTERO", 23) ]    

for c in candidate_tuples:
    cand = Candidate (c[0], c[1])
    candidates[cand.id] = cand

from_line_re = re.compile ("^From: *(.*)")
member_address_re = re.compile (">? *Member Address: *([^ ]*)")
auth_token_re = re.compile (">? *Validation Token: *(.*)")
vote_re = re.compile (">? *([A-Z- ]+) *\(ID# *([0-9]+)\)")

ballots = []
current_ballot = 0

filename = sys.argv[1]      # mail archive file 
secret_cookie = sys.argv[2] # secret cookie
voter_list = sys.argv[3]    # list of valid voter addresses

# hash from valid addresses to whether they have sent in a ballot yet
valid_addresses = {}

voter_handle = open (voter_list)
for voter_addr in voter_handle.readlines ():
    valid_addresses[string.strip (voter_addr)] = 0

handle = open (filename)
lines = handle.readlines ()
for line in lines:

    match = from_line_re.match (line)
    if match:
        email = string.strip (match.group (1))
        if current_ballot:
            ballots.append (current_ballot)
        current_ballot = Ballot ()
        current_ballot.email = email

        continue

    match = member_address_re.match (line)
    if match:
        member = string.strip (match.group (1))
        if (current_ballot.member):
            print "Duplicate member address in ballot from '%s' - duplicates ''%s', '%s'" % (current_ballot.email, current_ballot.member, member)
        else:        
            current_ballot.member = member

        continue
            
    match = auth_token_re.match (line)
    if match:
        token = string.strip (match.group (1))
        if (current_ballot.token):
            print "Duplicate auth token in ballot from '%s' - duplicates '%s', '%s'" % (current_ballot.email, current_ballot.token, token)
        else:
            current_ballot.token = token

        continue

    match = vote_re.match (line)
    if match:
        name = string.strip (match.group (1))
        id = string.strip (match.group (2))

        id = int(id)

        if not candidates.has_key (id):
            print "Unknown candidate '%s' ID %d in ballot from '%s'" % (name, id, current_ballot.email)
        elif not candidates[id].name == name:
            print "Candidate name '%s' for ID '%s' doesn't match, expected '%s'" % (name, id, candidates[id].name)    
        else:
            current_ballot.add_vote (name, id)

        continue
    
if current_ballot:
    ballots.append (current_ballot)    
        
handle.close ()

def contains_dups (b):
    dups = {}
    for v in b.votes:
        id = v[1]
        if dups.has_key (id):
            return 1
        dups[id] = 1
    return 0

dup_tokens = {}
def md5_is_bad (b):
    key = b.member + secret_cookie
    m = md5.new (key)
    digest = m.digest ()
    # convert to hex, python 2.0 has hexdigest() but this one I'm using
    # apparently does not
    token = ""
    for num in digest:
        token = token + ("%02x" % (ord(num),))
    if token == b.token:
        if dup_tokens.has_key (token):
            print "Auth token occurs twice, someone voted more than once"
            return 0
        else:
            dup_tokens[token] = 1
        return 0
    else:
        print "Bad auth token is %s hashed from '%s'" % (token, key)
        return 1

def valid_voter (addr):
    return valid_addresses.has_key (addr)

valid_ballots = {}

i = 0
for b in ballots:
    error = 0
    if not b.member:
        error = "missing member address"
    elif not b.token:
        error = "missing auth token"
    elif len (b.votes) > 11:
        error = "too many votes (%d votes)" % len (b.votes)
    elif len (b.votes) == 0:
        error = "didn't list any candidates"
    elif contains_dups (b):
        error = "contains duplicate votes for the same candidate"
    elif md5_is_bad (b):
        error = "bad authentication token"
    elif not valid_voter (b.member):
        error = "ballot from someone not on the list of valid voters"
    else:
        if valid_ballots.has_key (b.token):
            old = valid_ballots[b.token]
            print "Overriding previous valid ballot %d from %s with new ballot %d" % (old[1], old[0].email, i)
        valid_ballots[b.token] = (b, i)

    if error:
        print "Ignoring ballot %d from '%s' due to: %s" % (i, b.email, error)
        
    i = i + 1

def tupcmp (a, b):
    return cmp (a[1], b[1])

## Print results only after all errors have been printed, so
## we don't lose any errors.
valids = valid_ballots.values ()
valids.sort (tupcmp)
for (b, i) in valids:
    print "Ballot %d:" % i

    print "  From:   " + b.email
    print "  Member: " + b.member
    print "  Token:  " + b.token
    print "  Voted for %d candidates:" % len (b.votes)

    voted_for = []

    valid_addresses[b.member] = 1

    for v in b.votes:
        id = v[1]
        candidates[id].count = candidates[id].count + 1
	candidates[id].voters.append (b.member)
        voted_for.append (candidates[id].name)

    for v in voted_for:
        print "   " + v

print "The following members did not vote:"
for addr in valid_addresses.keys ():
    if not valid_addresses[addr]:
        print addr

def cmpcand (a, b):
    return cmp (a.count, b.count)

cand_list = candidates.values ()
cand_list.sort (cmpcand)

print ""
print ""
print "ELECTION RESULTS:"

print " %d of %d members cast a valid ballot" % (len (valids), len (valid_addresses.keys()))

for c in cand_list:
    print "  %s (%d votes)" % (c.name, c.count)