#!/usr/bin/env python
###
### Generate results, rankings, and team scores for the UCD Sprint Triathlon.
### Not the prettiest code in the world, but it works.  Feel free to take it
### and modify it for use with your own race if you like.
###
### Matt Roper <matt@mattrope.com>
###

import string
import re
import math

# Race data
data = []

def main():
    fp = open("athletes.db", "r")
    line = fp.readline()

    # First line was headers; ignore it and proceed with data
    line = fp.readline()
    while line:
        line = line[:-1]
        [raceno, lname, fname, gender, team, wave, penalty, train, finish] = line.split(";")

        # Break finish time into components
        if string.strip(finish):
            (h, m, s) = finish.split(":")
        else:
            h = m = s = 0

        # Break penalty into components
        if string.strip(penalty):
            (pm, ps) = penalty.split(":")
        else:
            pm = ps = 0

        # Break train delay into components
        if string.strip(train):
            (tm, ts) = train.split(":")
        else:
            tm = ts = 0

        data.append({
            "raceno"  : string.strip(raceno),
            "lname"   : string.strip(lname),
            "fname"   : string.strip(fname),
            "gender"  : string.strip(gender),
            "team"    : string.strip(team),
            "wave"    : int(wave),
            "penalty" : int(pm)*60 + int(ps),
            "train"   : int(tm)*60 + int(ts),
            "finish"  : int(h)*3600 + int(m)*60 + int(s)
            })
        line = fp.readline()
    fp.close()

    # Extract the finishers (i.e., those with a non-empty finish time)
    finishers = filter(lambda x: x['finish'] != 0, data)

    # Calculate final time based on wave and penalties
    for f in finishers:
        f['final'] = f['finish'] - (f['wave']-1)*20*60 + f['penalty'] - f['train']
        
    # Sort the finishers
    finishers.sort(lambda x, y: x['final'] - y['final'])

    # Separate collegiate, non-collegiate
    collegiate = filter(lambda x: x['team'] and x['team'][0] != "*", finishers)
    noncollegiate = filter(lambda x: not x['team'] or x['team'][0] == "*", finishers)

    # Separate collegiate male, collegiate female
    cm = filter(lambda x: x['gender'] == "m", collegiate)
    cf = filter(lambda x: x['gender'] != "m", collegiate)

    # Score the collegiate men
    scores = {}
    counted = {}
    place = 1
    for athlete in cm:
        # Scores go 50 deep
        if place > 50:
            break
        
        # How many points?
        athlete["points"] = placepoints(place)
        place += 1
        
        # Increase team score?
        team = athlete["team"]
        count = counted.get(team, 0)
        if count >= 7:
            continue
        else:
            score = scores.get(team, 0)
            score += athlete["points"]
            scores[team] = score
            counted[team] = count + 1

    # Sort the team scores
    items = scores.items()
    sortedscores = [ [v[1],v[0]] for v in items]
    sortedscores.sort()
    sortedscores.reverse()
    print "[ Team Scores (Men) ]"
    for [s, t] in sortedscores:
        print "%15s -- %4d" % (t, s)
    menscores = scores

    # Score the collegiate women
    scores = {}
    counted = {}
    place = 1
    for athlete in cf:
        # Scores go 50 deep
        if place > 50:
            break
        
        # How many points?
        athlete["points"] = placepoints(place)
        place += 1
        
        # Increase team score?
        team = athlete["team"]
        count = counted.get(team, 0)
        if count >= 7:
            continue
        else:
            score = scores.get(team, 0)
            score += athlete["points"]
            scores[team] = score
            counted[team] = count + 1

    # Sort the team scores
    items = scores.items()
    sortedscores = [ [v[1],v[0]] for v in items]
    sortedscores.sort()
    sortedscores.reverse()
    print "\n[ Team Scores (Women) ]"
    for [s, t] in sortedscores:
        print "%15s -- %4d" % (t, s)
    womenscores = scores

    # Combined team scores; 'scores' still holds women's scores
    for t in menscores.keys():
        scores[t] = menscores[t] + womenscores.get(t, 0)

    # Re-sort
    items = scores.items()
    sortedscores = [ [v[1],v[0]] for v in items]
    sortedscores.sort()
    sortedscores.reverse()
    print "\n[ Team Scores (Combined) ]"
    for [s, t] in sortedscores:
        print "%15s -- %4d" % (t, s)


    print "\n[ Collegiate Men ]"
    for x in cm:
        display_collegiate(x)

    print "\n[ Collegiate Women ]"
    for x in cf:
        display_collegiate(x)

    # Okay, now just the open finishers
    print "\n[ Open Division Rankings: Women 15 & Under ]"
    div = filter(lambda x: x['team'] == "*W15", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 16-19 ]"
    div = filter(lambda x: x['team'] == "*W16", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 16-19]"
    div = filter(lambda x: x['team'] == "*M16", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 20-25 ]"
    div = filter(lambda x: x['team'] == "*W20", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 20-25]"
    div = filter(lambda x: x['team'] == "*M20", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 26-30 ]"
    div = filter(lambda x: x['team'] == "*W26", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 26-30]"
    div = filter(lambda x: x['team'] == "*M26", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 31-35 ]"
    div = filter(lambda x: x['team'] == "*W31", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 31-35 ]"
    div = filter(lambda x: x['team'] == "*M31", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 36-40 ]"
    div = filter(lambda x: x['team'] == "*W36", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 36-40 ]"
    div = filter(lambda x: x['team'] == "*M36", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 41-45 ]"
    div = filter(lambda x: x['team'] == "*W41", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 41-45 ]"
    div = filter(lambda x: x['team'] == "*M41", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 46-50 ]"
    div = filter(lambda x: x['team'] == "*W46", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 46-50 ]"
    div = filter(lambda x: x['team'] == "*M46", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 51-55 ]"
    div = filter(lambda x: x['team'] == "*W51", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 51-55 ]"
    div = filter(lambda x: x['team'] == "*M51", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 56-60 ]"
    div = filter(lambda x: x['team'] == "*W56", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 56-60 ]"
    div = filter(lambda x: x['team'] == "*M56", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 61-65 ]"
    div = filter(lambda x: x['team'] == "*W61", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 61-65 ]"
    div = filter(lambda x: x['team'] == "*M61", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Women 66-70 ]"
    div = filter(lambda x: x['team'] == "*W66", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Open Division Rankings: Men 66-70 ]"
    div = filter(lambda x: x['team'] == "*M66", noncollegiate)
    for x in div: display_open(x)

    print "\n[ Relay Rankings ]"
    div = filter(lambda x: x['team'] == "*relay", noncollegiate)
    for x in div: display_open(x)

    print "\n[ No Age/Division Provided ]"
    div = filter(lambda x: x['team'] == "", noncollegiate)
    for x in div: display_open(x)


    ## Debug: all finishers sorted on clock time, not finish time
    #print "\n[ All Finishers ]"
    #finishers.sort(lambda x, y: x['finish'] - y['finish'])
    #for x in finishers:
    #    print "%s  %-25s %s  %s" % (x['raceno'], x['lname'] + ", " + x['fname'], displaytime(x['finish']), displaytime(x['final']))


def displaytime(t):
    s = t % 60
    t /= 60
    m = t % 60
    t /= 60
    h = t
    return "%d:%02d:%02d" % (h, m, s)

def displayshorttime(t):
    s = t % 60
    t /= 60
    m = t % 60
    t /= 60
    h = t
    return "%d:%02d" % (m, s)

def placepoints(place):
    # Since there were two races on our race weekend, only half of the usual
    # points are awarded:  1st gets 1st place points, 2nd gets 3rd place points,
    # 3rd gets 5th place points, etc.
    place = place * 2 - 1
    if place > 50: return 0
    
    # Return the number of points awarded for this place.  Note the extra 0.5
    # added on before we take the floor (int) function -- even though the
    # constitution (and formula) says nothing about rounding, the example
    # points shown are rounded to the nearest integer rather than being
    # truncated with the floor function.  It also appears that the other
    # collegiate races have scored with rounding, so I'll follow their lead,
    # even though I think this is technically wrong.
    return int(200*(1-math.log10(place)/math.log10(51)) + 0.5)

def display_collegiate(x):
    # Display a collegiate ranking, with adjustments for train or drafting
    adjust = ""
    if x['train']:
        adjust = "-%s (train) " % displayshorttime(x['train'])
    if x['penalty']:
        adjust += "+%s (drafting)" % displayshorttime(x['penalty'])
    print "%s  %-25s %-12s %-20s %s %4d points" % (x['raceno'], x['lname'] + ", " + x['fname'], x['team'], adjust, displaytime(x['final']), x.get('points', 0))

def display_open(x):
    # Display an open ranking, with adjustments for train or drafting
    adjust = ""
    if x['train']:
        adjust = "-%s (train) " % displayshorttime(x['train'])
    if x['penalty']:
        adjust += "+%s (drafting)" % displayshorttime(x['penalty'])
    print "%s  %-25s %-20s %s" % (x['raceno'], x['lname'] + ", " + x['fname'], adjust, displaytime(x['final']))

main()
