from ballot import ballot2form, form2ballot, blank_ballot, sign, uuid, regex_email, rsakeys
from ranking_algorithms import iro, borda, schulze
import re

def index():
    return dict()

@auth.requires_login()
def elections():
    response.subtitle = T('My Elections')
    elections = db(db.election.created_by==auth.user.id).select(
        orderby=~db.election.created_on)
    ballots = db(db.voter.email == auth.user.email)(
        db.voter.voted==False)(db.voter.election_id==db.election.id)(
        (db.election.deadline==None)|(db.election.deadline>request.now)).select()
    return dict(elections=elections,ballots=ballots)

@auth.requires(auth.user and auth.user.is_manager)
def edit():
    response.subtitle = T('Edit Ballot')
    election = db.election(request.args(0,cast=int,default=0))
    if election and not election.created_by==auth.user_id:
        redirect(URL('not_authorized'))
    if not election:
        (pubkey, privkey) = rsakeys()
        db.election.voters.default = auth.user.email
        db.election.managers.default = auth.user.email
        db.election.public_key.default = pubkey
        db.election.private_key.default = privkey        
    form = SQLFORM(db.election,election,deletable=True,
                   submit_button="Save and Preview").process()
    if form.accepted: redirect(URL('start',args=form.vars.id))
    return dict(form=form)

@auth.requires(auth.user and auth.user.is_manager)
def start():    
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    check_closed(election)
    response.subtitle = election.title+T(' / Start')
    demo = ballot2form(election.ballot_model)
    return dict(demo=demo,election=election)

@auth.requires(auth.user and auth.user.is_manager)
def start_callback():
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    check_closed(election)
    form = SQLFORM.factory(
        submit_button=T('Email Voters and Start Election Now!'))
    form.element(_type='submit').add_class('btn')
    failures = []
    emails = []
    owner_email = election.created_by.email
    if form.process().accepted:
        ballot_counter = db(db.ballot.election_id==election.id).count()
        for email in regex_email.findall(election.voters):
            email = email.lower()
            voter = db(db.voter.election_id==election.id)\
                (db.voter.email==email).select().first()
            if voter:
                voter_uuid = voter.voter_uuid
            else:
                # create a voter
                voter_uuid = 'voter-'+uuid()
                voter = db.voter.insert(
                    election_id=election.id,
                    voter_uuid=voter_uuid,
                    email=email,invited_on=None)
                # create a ballot
                ballot_counter+=1
                ballot_uuid = 'ballot-%i-%.6i' % (election.id,ballot_counter)
                blank_ballot_content = blank_ballot(ballot_uuid)
                signature = 'signature-'+sign(blank_ballot_content,
                                              election.private_key)
                db.ballot.insert(
                    election_id=election.id,
                    ballot_content = blank_ballot_content,
                    ballot_uuid=ballot_uuid,
                    signature = signature)
            link_vote = URL('vote',args=(election.id,voter_uuid),scheme=SCHEME)
            link_ballots = URL('ballots',args=election.id,scheme=SCHEME)
            link_results = URL('results',args=election.id,scheme=SCHEME)
            body = message_replace(election.vote_email,
                                      election_id = election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      link=link_vote,
                                      link_ballots=link_ballots,
                                      link_results=link_results)
            subject = '%s [%s]' % (election.title, election.id)
            emails.append((voter,email,subject,body))
        db.commit()
        sender = election.email_sender or mail.settings.sender
        for voter, to, subject, body in emails:
            if mail.send(to=to, subject=subject, message=body, sender=sender):
                db(db.voter.id==voter).update(invited_on=request.now)
            else:
                failures.append(to)
        if not failures:
            session.flash = T('Emails sent successfully')
            redirect(URL('elections'),client_side=True)
    return dict(form=form,failures=failures,election=election)


@auth.requires(False) # for now this is disabled
def self_service():
    form = SQLFORM.factory(
        Field('election_id','integer',requires=IS_NOT_EMPTY()),
        Field('email',requires=IS_EMAIL()))
    if form.process().accepted:
        election = db.election(form.vars.id)
        if not election: form.errors['election_id'] = 'Invalid'
        voter = db.voter(election=election_id,email=form.vars.email)
        if not voter: form.errors['voter'] = 'Invalid'
        if voter.voted:
            response.flash = T('User has voted alreday')
        else:
            link_vote = URL('vote',args=(election.id,voter_uuid),scheme=SCHEME)
            link_ballots = URL('ballots',args=election.id,scheme=SCHEME)
            link_results = URL('results',args=election.id,scheme=SCHEME)
            body = message_replace(election.vote_email,
                                      election_id = election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      link=link_vote,
                                      link_ballots=link_ballots,
                                      link_results=link_results)
            sender = election.email_sender or mail.settings.sender
            if mail.send(to=voter.email, subject=election.title, message=body, sender=sender):
                response.flash = T('Email sent')
            else:
                response.flash = T('Unable to send email')
    return dict(form=form)
                                

@auth.requires(auth.user and auth.user.is_manager)
def reminders():    
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    response.subtitle = election.title+T(' / Reminders')
    return dict(election=election)

@auth.requires(auth.user and auth.user.is_manager)
def reminders_callback():
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    owner_email = election.created_by.email
    failures = []
    emails = []
    fields = []
    for email in regex_email.findall(election.voters):
        voter = db(db.voter.election_id==election.id)\
            (db.voter.email==email).select().first()
        voter_uuid = voter.voter_uuid
        key = 'voter_%s' % voter.id
        fields.append(Field(key,'boolean',default=not voter.voted,
                            label = voter.email))
        if key in request.post_vars:            
            link = URL('vote',args=(election.id,voter_uuid),scheme=SCHEME)
            link_ballots = URL('ballots',args=election.id,scheme=SCHEME)
            link_results = URL('results',args=election.id,scheme=SCHEME)
            body = message_replace(election.vote_email,
                                      election_id = election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      link=link,
                                      link_ballots=link_ballots,
                                      link_results=link_results)
            subject = '%s [%s]' % (election.title, election.id)
            emails.append((email,subject,body))
    form = SQLFORM.factory(*fields).process()
    if form.accepted:
        sender = election.email_sender or mail.settings.sender
        for to, subject, body in emails:
            if not mail.send(to=to, subject=subject, message=body, sender=sender):
                                 
                failures.append(email)
        if not failures:
            session.flash = T('Emails sent successfully')
            redirect(URL('elections'),client_side=True)
    return dict(form=form,failures=failures,election=election)

@auth.requires(auth.user and auth.user.is_manager)
def recompute_results():
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    compute_results(election)
    redirect(URL('results',args=election.id))

def compute_results(election):
    query = db.ballot.election_id==election.id
    voted_ballots = db(query)(db.ballot.voted==True).select()
    counters = {}
    rankers = {}
    for k,ballot in enumerate(voted_ballots):        
        for name in ballot.results:
            # name is the name of a group as in {{name:ranking}}
            # scheme is "ranking" or "checkbox" (default)
            # value is the <input value="value"> assigned to this checkbox or input

            # INPORTANT ONLY SUPPORT SIMPLE MAJORITY
            key = name +'/simple-majority/' + ballot.results[name]
            (name,scheme,value) = key.split('/',3)
            if scheme == 'simple-majority':
                # counters[key] counts how many times this checkbox was checked
                counters[key] = counters.get(key,0) + 1
                    
            elif scheme == 'ranking':
                raise NotImplementedError
                # rankers[name] = [[2,1,3],[3,1,2],[1,2,3],...]
                #                 The sublists in rankers mean:
                #                 [[my first-preferred candidate is
                #                   the candidate whose identifying
                #                   number on the original ballot was
                #                   <the number given in this first
                #                   position here>],
                #                  [my second-preferred candidate is
                #                   the candidate whose identifying
                #                   number on the original ballot was
                #                   <the number given in this second
                #                   position here>],
                #                  [my third-preferred candidate is
                #                   the candidate whose identifying
                #                   number on the original ballot was
                #                   <the number given in this third
                #                   position here>], ...]
                #
                # len(rankers[name]) = len(voted_ballots)
                # rankers[name][i][0] = ...?
                if not name in rankers:
                    rankers[name] = []
                if len(rankers[name])<k+1:
                    rankers[name].append([])
                vote = rankers[name][-1]
                print "ballot id:",ballot.id, "key:",key, "results[key]:",results[key], "vote:",vote
                ranking = int(results[key])
                d = ranking-len(vote)
                if d>0:
                    print "vote before:", vote
                    vote+=[0]*d
                    print "vote after: ", vote
                vote[ranking-1] = value
            else:
                raise RuntimeError("Invalid Voting Scheme")    

    for name in rankers:
        votes = rankers[name]
        cmajority = borda(votes,mode='exponential')
        ciro = iro(votes)
        cschulze = schulze(votes)
        key = name+'/simple-majority/'+k
        for (r,k) in cmajority:
            counters[key] = 'M:%s' % r
        for (r,k) in ciro:
            counters[key] += ' I:%s' % r
        for (r,k) in cschulze:
            counters[key] += ' S:%s' % r

    print counters
    election.update_record(counters=counters)

#@cache(request.env.path_info,time_expire=300,cache_model=cache.ram)
def results():
    id = request.args(0,cast=int) or redirect(URL('index'))
    election = db.election(id) or redirect(URL('index'))
    if auth.user_id!=election.created_by and \
            not(election.deadline and request.now>election.deadline):
        session.flash = T('Results not yet available')
        redirect(URL('index'))
    response.subtitle = election.title + T(' / Results')
    if (DEBUG_MODE or not election.counters or
        not election.deadline or request.now<=election.deadline):
        compute_results(election)
    form = ballot2form(election.ballot_model, counters=election.counters)
    return dict(form=form,election=election)

def hash_ballot(text):
    import re
    text = text.replace('checked="checked" ','')
    text = text.replace('disabled="disabled" ','')
    text = re.sub('value="\d+"','',text)
    text = re.sub('ballot\S+','',text)
    return hash(text)

def ballots():
    election = db.election(request.args(0,cast=int)) or \
        redirect(URL('invalid_link'))
    response.subtitle = election.title + T(' / Ballots')
    ballots = db(db.ballot.election_id==election.id).select(        
        orderby=db.ballot.ballot_uuid)
    tampered = len(set(hash_ballot(b.ballot_content) 
                       for b in ballots if b.voted))>1
    return dict(ballots=ballots,election=election, tampered=tampered)

# @auth.requires(auth.user and auth.user.is_manager)
def email_voter_and_managers(election,voter,ballot,body):
    import cStringIO
    attachment = mail.Attachment(
        filename=ballot.ballot_uuid+'.html',
        payload=cStringIO.StringIO(ballot.ballot_content))
    sender = election.email_sender or mail.settings.sender
    ret = mail.send(to=voter.email,
                    subject='Receipt for %s' % election.title,
                    message=body,attachments=[attachment],
                    sender=sender)
    mail.send(to=regex_email.findall(election.managers),
              subject='Copy of Receipt for %s' % election.title,
              message=body,
              attachments=[attachment],
              sender=sender)
    return ret

def check_closed(election):
    if election.closed:
        session.flash = T('Election already closed')
        redirect(URL('elections'))

@auth.requires(auth.user and auth.user.is_manager)
def close_election():
    import zipfile, os
    election = db.election(request.args(0,cast=int)) or \
        redirect(URL('invalid_link'))
    #check_closed(election)
    response.subtitle = election.title
    dialog = FORM.confirm(T('Close'),
                          {T('Cancel'):URL('elections')})
    if dialog.accepted:
        election.update_record(deadline=request.now)
        voters = db(db.voter.election_id==election.id)\
            (db.voter.voted==False).select()
        ballots = db(db.ballot.election_id==election.id)\
            (db.ballot.voted==False)(db.ballot.assigned==False).select()
        if ballots and len(voters)!=len(ballots):
            session.flash = T('Voted corrupted ballots/voter mismatch')
            redirect(URL('elections'))
        owner_email = election.created_by.email
        for i in range(len(voters)):
            voter, ballot = voters[i], ballots[i]
            link = URL('ballot',args=ballot.ballot_uuid,scheme='http')
            body = message_replace(election.not_voted_email,
                                      election_id=election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      signature=ballot.signature,link=link)
            email_voter_and_managers(election,voter,ballot,body)
            ballot.update_record(assigned=True)
        compute_results(election)
        zippath = os.path.join(request.folder,'static','zips')
        if not os.path.exists(zippath):
            os.mkdir(zippath)
        archive = zipfile.ZipFile(
            os.path.join(zippath,'%s.zip' % election.id),'w')
        dbset = db(db.ballot.election_id==election.id)
        ballots = dbset.select()
        for ballot in ballots:
            archive.writestr(ballot.ballot_uuid,ballot.ballot_content)
        ballots = dbset.select(
            db.ballot.election_id,
            db.ballot.ballot_uuid,
            db.ballot.assigned,
            db.ballot.voted,
            db.ballot.voted_on,
            db.ballot.signature,
            orderby=db.ballot.ballot_uuid)
        archive.writestr('ballots.csv',str(ballots))
        archive.close()
        election.update_record(closed=True)
        session.flash = 'Election Closed!'
        redirect(URL('results',args=election.id))
    return dict(dialog=dialog,election=election)

def ballot():
    ballot_uuid = request.args(0) or redirect(URL('index'))
    signature = request.args(1)
    election_id = int(ballot_uuid.split('-')[1])
    election = db.election(election_id) or redirect(URL('index'))
    ballot = db.ballot(election_id=election.id,ballot_uuid=ballot_uuid) \
        or redirect(URL('invalid_link'))
    if (not election.deadline or election.deadline>request.now) \
            and ballot.signature!=signature:
        session.flash = "your ballot is not visible until election is closed"
        redirect(URL('index'))
    response.subtitle = election.title + T(' / Ballot')
    return dict(ballot=ballot,election=election)

def recorded():
    return dict()

def ballot_verifier():
    response.headers['Content-Type'] = 'text/plain'
    return ballot()

def vote():    
    import hashlib
    response.menu = []
    election_id = request.args(0,cast=int)
    voter_uuid = request.args(1)
    election = db.election(election_id) or redirect(URL('invalid_link'))       
    voter = db(db.voter.election_id==election_id)\
        (db.voter.voter_uuid==voter_uuid).select().first() or \
        redirect(URL('invalid_link'))
    if not DEBUG_MODE and voter.voted:
        redirect(URL('voted_already'))
    
    if election.deadline and request.now>election.deadline:
        session.flash = T('Election is closed')
        if voter.voted:
            session.flash += T('Your vote was recorded')
        else:
            session.flash += T('Your vote was NOT recorded')
        redirect(URL('results',args=election.id))
    response.subtitle = election.title + ' / Vote'
    form = ballot2form(election.ballot_model, readonly=False)
    form.process()
    if form.accepted:
        results = form.vars
        for_update = not db._uri.startswith('sqlite') # not suported by sqlite
        #if not for_update: db.executesql('begin immediate transaction;')
        ballot = db(db.ballot.election_id==election_id)\
            (db.ballot.voted==False).select(
            orderby='<random>',limitby=(0,1),for_update=for_update).first() \
            or redirect(URL('no_more_ballots'))
        ballot_content = form2ballot(election.ballot_model,
                                     token=ballot.ballot_uuid,
                                     vars=request.post_vars,results=results)
        signature = 'signature-'+sign(ballot_content,election.private_key)
        ballot.update_record(results=results,
                             ballot_content=ballot_content,
                             signature=signature,
                             voted=True,assigned=True,voted_on=request.now)
        voter.update_record(voted=True)
        link = URL('ballot',args=(ballot.ballot_uuid,ballot.signature), scheme='http')

        body = message_replace(election.voted_email,link=link,
                                  election_id=election.id,
                                  owner_email = election.created_by.email,
                                  title=election.title,signature=signature)
        emailed = email_voter_and_managers(election,voter,ballot,body)
        session.flash = \
            T('Your vote was recorded and we sent you an email') \
            if emailed else \
            T('Your vote was recorded but we failed to email you')
        redirect(URL('recorded',vars=dict(link=link)))
    return dict(form=form)

def user():
    return dict(form=auth())

def invalid_link():
    return dict(message=T('Invalid Link'))

def voted_already():
    return dict(message=T('You already voted'))

def not_authorized():
    return dict(message=T('Not Authorized'))

def no_more_ballots():
    return dict(message=T('Run out of ballots. Your vote was not recorded'))

@auth.requires(auth.user and auth.user.is_manager)
def voters_csv():
    election = db.election(request.args(0,cast=int,default=0),created_by=auth.user.id)
    return db(db.voter.election_id==election.id).select(
        db.voter.election_id,db.voter.email,db.voter.voted).as_csv()

def features():
    return locals()

def support():
    return locals()

def contactus():
    return locals()

from ballot import ballot2form, form2ballot, blank_ballot, sign, uuid, regex_email, rsakeys
from ranking_algorithms import iro, borda, schulze
import re

def index():
    return dict()

@auth.requires_login()
def elections():
    response.subtitle = T('My Elections')
    elections = db(db.election.created_by==auth.user.id).select(
        orderby=~db.election.created_on)
    ballots = db(db.voter.email == auth.user.email)(
        db.voter.voted==False)(db.voter.election_id==db.election.id)(
        (db.election.deadline==None)|(db.election.deadline>request.now)).select()
    return dict(elections=elections,ballots=ballots)

@auth.requires(auth.user and auth.user.is_manager)
def edit():
    response.subtitle = T('Edit Ballot')
    election = db.election(request.args(0,cast=int,default=0))
    if election and not election.created_by==auth.user_id:
        redirect(URL('not_authorized'))
    if not election:
        (pubkey, privkey) = rsakeys()
        db.election.voters.default = auth.user.email
        db.election.managers.default = auth.user.email
        db.election.public_key.default = pubkey
        db.election.private_key.default = privkey        
    form = SQLFORM(db.election,election,deletable=True,
                   submit_button="Save and Preview").process()
    if form.accepted: redirect(URL('start',args=form.vars.id))
    return dict(form=form)

@auth.requires(auth.user and auth.user.is_manager)
def start():    
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    check_closed(election)
    response.subtitle = election.title+T(' / Start')
    demo = ballot2form(election.ballot_model)
    return dict(demo=demo,election=election)

@auth.requires(auth.user and auth.user.is_manager)
def start_callback():
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    check_closed(election)
    form = SQLFORM.factory(
        submit_button=T('Email Voters and Start Election Now!'))
    form.element(_type='submit').add_class('btn')
    failures = []
    emails = []
    owner_email = election.created_by.email
    if form.process().accepted:
        ballot_counter = db(db.ballot.election_id==election.id).count()
        for email in regex_email.findall(election.voters):
            email = email.lower()
            voter = db(db.voter.election_id==election.id)\
                (db.voter.email==email).select().first()
            if voter:
                voter_uuid = voter.voter_uuid
            else:
                # create a voter
                voter_uuid = 'voter-'+uuid()
                voter = db.voter.insert(
                    election_id=election.id,
                    voter_uuid=voter_uuid,
                    email=email,invited_on=None)
                # create a ballot
                ballot_counter+=1
                ballot_uuid = 'ballot-%i-%.6i' % (election.id,ballot_counter)
                blank_ballot_content = blank_ballot(ballot_uuid)
                signature = 'signature-'+sign(blank_ballot_content,
                                              election.private_key)
                db.ballot.insert(
                    election_id=election.id,
                    ballot_content = blank_ballot_content,
                    ballot_uuid=ballot_uuid,
                    signature = signature)
            link_vote = URL('vote',args=(election.id,voter_uuid),scheme=SCHEME)
            link_ballots = URL('ballots',args=election.id,scheme=SCHEME)
            link_results = URL('results',args=election.id,scheme=SCHEME)
            body = message_replace(election.vote_email,
                                      election_id = election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      link=link_vote,
                                      link_ballots=link_ballots,
                                      link_results=link_results)
            subject = '%s [%s]' % (election.title, election.id)
            emails.append((voter,email,subject,body))
        db.commit()
        sender = election.email_sender or mail.settings.sender
        for voter, to, subject, body in emails:
            if mail.send(to=to, subject=subject, message=body, sender=sender):
                db(db.voter.id==voter).update(invited_on=request.now)
            else:
                failures.append(to)
        if not failures:
            session.flash = T('Emails sent successfully')
            redirect(URL('elections'),client_side=True)
    return dict(form=form,failures=failures,election=election)


@auth.requires(False) # for now this is disabled
def self_service():
    form = SQLFORM.factory(
        Field('election_id','integer',requires=IS_NOT_EMPTY()),
        Field('email',requires=IS_EMAIL()))
    if form.process().accepted:
        election = db.election(form.vars.id)
        if not election: form.errors['election_id'] = 'Invalid'
        voter = db.voter(election=election_id,email=form.vars.email)
        if not voter: form.errors['voter'] = 'Invalid'
        if voter.voted:
            response.flash = T('User has voted alreday')
        else:
            link_vote = URL('vote',args=(election.id,voter_uuid),scheme=SCHEME)
            link_ballots = URL('ballots',args=election.id,scheme=SCHEME)
            link_results = URL('results',args=election.id,scheme=SCHEME)
            body = message_replace(election.vote_email,
                                      election_id = election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      link=link_vote,
                                      link_ballots=link_ballots,
                                      link_results=link_results)
            sender = election.email_sender or mail.settings.sender
            if mail.send(to=voter.email, subject=election.title, message=body, sender=sender):
                response.flash = T('Email sent')
            else:
                response.flash = T('Unable to send email')
    return dict(form=form)
                                

@auth.requires(auth.user and auth.user.is_manager)
def reminders():    
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    response.subtitle = election.title+T(' / Reminders')
    return dict(election=election)

@auth.requires(auth.user and auth.user.is_manager)
def reminders_callback():
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    owner_email = election.created_by.email
    failures = []
    emails = []
    fields = []
    for email in regex_email.findall(election.voters):
        voter = db(db.voter.election_id==election.id)\
            (db.voter.email==email).select().first()
        voter_uuid = voter.voter_uuid
        key = 'voter_%s' % voter.id
        fields.append(Field(key,'boolean',default=not voter.voted,
                            label = voter.email))
        if key in request.post_vars:            
            link = URL('vote',args=(election.id,voter_uuid),scheme=SCHEME)
            link_ballots = URL('ballots',args=election.id,scheme=SCHEME)
            link_results = URL('results',args=election.id,scheme=SCHEME)
            body = message_replace(election.vote_email,
                                      election_id = election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      link=link,
                                      link_ballots=link_ballots,
                                      link_results=link_results)
            subject = '%s [%s]' % (election.title, election.id)
            emails.append((email,subject,body))
    form = SQLFORM.factory(*fields).process()
    if form.accepted:
        sender = election.email_sender or mail.settings.sender
        for to, subject, body in emails:
            if not mail.send(to=to, subject=subject, message=body, sender=sender):
                                 
                failures.append(email)
        if not failures:
            session.flash = T('Emails sent successfully')
            redirect(URL('elections'),client_side=True)
    return dict(form=form,failures=failures,election=election)

@auth.requires(auth.user and auth.user.is_manager)
def recompute_results():
    election = db.election(request.args(0,cast=int)) or redirect(URL('index'))
    compute_results(election)
    redirect(URL('results',args=election.id))

def compute_results(election):
    query = db.ballot.election_id==election.id
    voted_ballots = db(query)(db.ballot.voted==True).select()
    counters = {}
    rankers = {}
    for k,ballot in enumerate(voted_ballots):        
        for name in ballot.results:
            # name is the name of a group as in {{name:ranking}}
            # scheme is "ranking" or "checkbox" (default)
            # value is the <input value="value"> assigned to this checkbox or input

            # INPORTANT ONLY SUPPORT SIMPLE MAJORITY
            key = name +'/simple-majority/' + ballot.results[name]
            (name,scheme,value) = key.split('/',3)
            if scheme == 'simple-majority':
                # counters[key] counts how many times this checkbox was checked
                counters[key] = counters.get(key,0) + 1
                    
            elif scheme == 'ranking':
                raise NotImplementedError
                # rankers[name] = [[2,1,3],[3,1,2],[1,2,3],...]
                #                 The sublists in rankers mean:
                #                 [[my first-preferred candidate is
                #                   the candidate whose identifying
                #                   number on the original ballot was
                #                   <the number given in this first
                #                   position here>],
                #                  [my second-preferred candidate is
                #                   the candidate whose identifying
                #                   number on the original ballot was
                #                   <the number given in this second
                #                   position here>],
                #                  [my third-preferred candidate is
                #                   the candidate whose identifying
                #                   number on the original ballot was
                #                   <the number given in this third
                #                   position here>], ...]
                #
                # len(rankers[name]) = len(voted_ballots)
                # rankers[name][i][0] = ...?
                if not name in rankers:
                    rankers[name] = []
                if len(rankers[name])<k+1:
                    rankers[name].append([])
                vote = rankers[name][-1]
                print "ballot id:",ballot.id, "key:",key, "results[key]:",results[key], "vote:",vote
                ranking = int(results[key])
                d = ranking-len(vote)
                if d>0:
                    print "vote before:", vote
                    vote+=[0]*d
                    print "vote after: ", vote
                vote[ranking-1] = value
            else:
                raise RuntimeError("Invalid Voting Scheme")    

    for name in rankers:
        votes = rankers[name]
        cmajority = borda(votes,mode='exponential')
        ciro = iro(votes)
        cschulze = schulze(votes)
        key = name+'/simple-majority/'+k
        for (r,k) in cmajority:
            counters[key] = 'M:%s' % r
        for (r,k) in ciro:
            counters[key] += ' I:%s' % r
        for (r,k) in cschulze:
            counters[key] += ' S:%s' % r

    print counters
    election.update_record(counters=counters)

#@cache(request.env.path_info,time_expire=300,cache_model=cache.ram)
def results():
    id = request.args(0,cast=int) or redirect(URL('index'))
    election = db.election(id) or redirect(URL('index'))
    if auth.user_id!=election.created_by and \
            not(election.deadline and request.now>election.deadline):
        session.flash = T('Results not yet available')
        redirect(URL('index'))
    response.subtitle = election.title + T(' / Results')
    if (DEBUG_MODE or not election.counters or
        not election.deadline or request.now<=election.deadline):
        compute_results(election)
    form = ballot2form(election.ballot_model, counters=election.counters)
    return dict(form=form,election=election)

def hash_ballot(text):
    import re
    text = text.replace('checked="checked" ','')
    text = text.replace('disabled="disabled" ','')
    text = re.sub('value="\d+"','',text)
    text = re.sub('ballot\S+','',text)
    return hash(text)

def ballots():
    election = db.election(request.args(0,cast=int)) or \
        redirect(URL('invalid_link'))
    response.subtitle = election.title + T(' / Ballots')
    ballots = db(db.ballot.election_id==election.id).select(        
        orderby=db.ballot.ballot_uuid)
    tampered = len(set(hash_ballot(b.ballot_content) 
                       for b in ballots if b.voted))>1
    return dict(ballots=ballots,election=election, tampered=tampered)

# @auth.requires(auth.user and auth.user.is_manager)
def email_voter_and_managers(election,voter,ballot,body):
    import cStringIO
    attachment = mail.Attachment(
        filename=ballot.ballot_uuid+'.html',
        payload=cStringIO.StringIO(ballot.ballot_content))
    sender = election.email_sender or mail.settings.sender
    ret = mail.send(to=voter.email,
                    subject='Receipt for %s' % election.title,
                    message=body,attachments=[attachment],
                    sender=sender)
    mail.send(to=regex_email.findall(election.managers),
              subject='Copy of Receipt for %s' % election.title,
              message=body,
              attachments=[attachment],
              sender=sender)
    return ret

def check_closed(election):
    if election.closed:
        session.flash = T('Election already closed')
        redirect(URL('elections'))

@auth.requires(auth.user and auth.user.is_manager)
def close_election():
    import zipfile, os
    election = db.election(request.args(0,cast=int)) or \
        redirect(URL('invalid_link'))
    #check_closed(election)
    response.subtitle = election.title
    dialog = FORM.confirm(T('Close'),
                          {T('Cancel'):URL('elections')})
    if dialog.accepted:
        election.update_record(deadline=request.now)
        voters = db(db.voter.election_id==election.id)\
            (db.voter.voted==False).select()
        ballots = db(db.ballot.election_id==election.id)\
            (db.ballot.voted==False)(db.ballot.assigned==False).select()
        if ballots and len(voters)!=len(ballots):
            session.flash = T('Voted corrupted ballots/voter mismatch')
            redirect(URL('elections'))
        owner_email = election.created_by.email
        for i in range(len(voters)):
            voter, ballot = voters[i], ballots[i]
            link = URL('ballot',args=ballot.ballot_uuid,scheme='http')
            body = message_replace(election.not_voted_email,
                                      election_id=election.id,
                                      owner_email = owner_email,
                                      title=election.title,
                                      signature=ballot.signature,link=link)
            email_voter_and_managers(election,voter,ballot,body)
            ballot.update_record(assigned=True)
        compute_results(election)
        zippath = os.path.join(request.folder,'static','zips')
        if not os.path.exists(zippath):
            os.mkdir(zippath)
        archive = zipfile.ZipFile(
            os.path.join(zippath,'%s.zip' % election.id),'w')
        dbset = db(db.ballot.election_id==election.id)
        ballots = dbset.select()
        for ballot in ballots:
            archive.writestr(ballot.ballot_uuid,ballot.ballot_content)
        ballots = dbset.select(
            db.ballot.election_id,
            db.ballot.ballot_uuid,
            db.ballot.assigned,
            db.ballot.voted,
            db.ballot.voted_on,
            db.ballot.signature,
            orderby=db.ballot.ballot_uuid)
        archive.writestr('ballots.csv',str(ballots))
        archive.close()
        election.update_record(closed=True)
        session.flash = 'Election Closed!'
        redirect(URL('results',args=election.id))
    return dict(dialog=dialog,election=election)

def ballot():
    ballot_uuid = request.args(0) or redirect(URL('index'))
    signature = request.args(1)
    election_id = int(ballot_uuid.split('-')[1])
    election = db.election(election_id) or redirect(URL('index'))
    ballot = db.ballot(election_id=election.id,ballot_uuid=ballot_uuid) \
        or redirect(URL('invalid_link'))
    if (not election.deadline or election.deadline>request.now) \
            and ballot.signature!=signature:
        session.flash = "your ballot is not visible until election is closed"
        redirect(URL('index'))
    response.subtitle = election.title + T(' / Ballot')
    return dict(ballot=ballot,election=election)

def recorded():
    return dict()

def ballot_verifier():
    response.headers['Content-Type'] = 'text/plain'
    return ballot()

def vote():    
    import hashlib
    response.menu = []
    election_id = request.args(0,cast=int)
    voter_uuid = request.args(1)
    election = db.election(election_id) or redirect(URL('invalid_link'))       
    voter = db(db.voter.election_id==election_id)\
        (db.voter.voter_uuid==voter_uuid).select().first() or \
        redirect(URL('invalid_link'))
    if not DEBUG_MODE and voter.voted:
        redirect(URL('voted_already'))
    
    if election.deadline and request.now>election.deadline:
        session.flash = T('Election is closed')
        if voter.voted:
            session.flash += T('Your vote was recorded')
        else:
            session.flash += T('Your vote was NOT recorded')
        redirect(URL('results',args=election.id))
    response.subtitle = election.title + ' / Vote'
    form = ballot2form(election.ballot_model, readonly=False)
    form.process()
    if form.accepted:
        results = form.vars
        for_update = not db._uri.startswith('sqlite') # not suported by sqlite
        #if not for_update: db.executesql('begin immediate transaction;')
        ballot = db(db.ballot.election_id==election_id)\
            (db.ballot.voted==False).select(
            orderby='<random>',limitby=(0,1),for_update=for_update).first() \
            or redirect(URL('no_more_ballots'))
        ballot_content = form2ballot(election.ballot_model,
                                     token=ballot.ballot_uuid,
                                     vars=request.post_vars,results=results)
        signature = 'signature-'+sign(ballot_content,election.private_key)
        ballot.update_record(results=results,
                             ballot_content=ballot_content,
                             signature=signature,
                             voted=True,assigned=True,voted_on=request.now)
        voter.update_record(voted=True)
        link = URL('ballot',args=(ballot.ballot_uuid,ballot.signature), scheme='http')

        body = message_replace(election.voted_email,link=link,
                                  election_id=election.id,
                                  owner_email = election.created_by.email,
                                  title=election.title,signature=signature)
        emailed = email_voter_and_managers(election,voter,ballot,body)
        session.flash = \
            T('Your vote was recorded and we sent you an email') \
            if emailed else \
            T('Your vote was recorded but we failed to email you')
        redirect(URL('recorded',vars=dict(link=link)))
    return dict(form=form)

def user():
    return dict(form=auth())

def invalid_link():
    return dict(message=T('Invalid Link'))

def voted_already():
    return dict(message=T('You already voted'))

def not_authorized():
    return dict(message=T('Not Authorized'))

def no_more_ballots():
    return dict(message=T('Run out of ballots. Your vote was not recorded'))

@auth.requires(auth.user and auth.user.is_manager)
def voters_csv():
    election = db.election(request.args(0,cast=int,default=0),created_by=auth.user.id)
    return db(db.voter.election_id==election.id).select(
        db.voter.election_id,db.voter.email,db.voter.voted).as_csv()

def features():
    return locals()

def support():
    return locals()

def contactus():
    return locals()

# create a voter # create a ballot # for now this is disabled # name is the name of a group as in {{name:ranking}} # scheme is "ranking" or "checkbox" (default) # value is the <input value="value"> assigned to this checkbox or input # INPORTANT ONLY SUPPORT SIMPLE MAJORITY # counters[key] counts how many times this checkbox was checked # rankers[name] = [[2,1,3],[3,1,2],[1,2,3],...] #                 The sublists in rankers mean: #                 [[my first-preferred candidate is #                   the candidate whose identifying #                   number on the original ballot was #                   <the number given in this first #                   position here>], #                  [my second-preferred candidate is #                   the candidate whose identifying #                   number on the original ballot was #                   <the number given in this second #                   position here>], #                  [my third-preferred candidate is #                   the candidate whose identifying #                   number on the original ballot was #                   <the number given in this third #                   position here>], ...] # # len(rankers[name]) = len(voted_ballots) # rankers[name][i][0] = ...? #@cache(request.env.path_info,time_expire=300,cache_model=cache.ram) # @auth.requires(auth.user and auth.user.is_manager) #check_closed(election) # not suported by sqlite #if not for_update: db.executesql('begin immediate transaction;')