#!/usr/bin/env python

# Author: Josh Holtrop
# Simple script to show me my week's hours as far as my Ubuntu box knows them

import os
import sys
import re
import argparse
from datetime import *

LOG_FILE = '/var/log/auth.log'
ADJUSTMENTS_FILE = os.path.expanduser('~/.hours')
ISO_DATE_FMT = '%Y-%m-%d'
HOURS_PER_DAY = 8.0

now = datetime.now()
monday = (now - timedelta(now.weekday())).date()

def get_date_from_day_spec(day_spec):
    if re.match(r'\d+', day_spec):
        for i in range(7):
            d = monday + timedelta(i)
            if d.day == int(day_spec):
                return d.strftime(ISO_DATE_FMT)
        return ''
    days = {
            'mon': 0,
            'tue': 1,
            'wed': 2,
            'thu': 3,
            'fri': 4,
            'sat': 5,
            'sun': 6
            }
    day_spec = day_spec.lower()
    if len(day_spec) > 3:
        day_spec = day_spec[:3]
    if day_spec in days:
        return (monday + timedelta(days[day_spec])).strftime(ISO_DATE_FMT)
    return ''

def get_adjustments():
    adjustments = {}
    if not os.path.isfile(ADJUSTMENTS_FILE):
        return adjustments
    f = open(ADJUSTMENTS_FILE, 'r')
    for line in iter(f.readline, ''):
        m = re.match(r'adj\s+(\S+)\s+(\S+)', line)
        if m is not None:
            adjustments[m.group(1)] = float(m.group(2))
    f.close()
    return adjustments

def save_adjustments(adjustments):
    f = open(ADJUSTMENTS_FILE, 'w')
    for a in adjustments:
        if datetime.strptime(a, ISO_DATE_FMT).date() >= monday:
            if adjustments[a] != 0.0:
                f.write('adj %s %f\n' % (a, adjustments[a]))
    f.close()

def get_dt_from_log_line(line):
    m = re.match(r'(\S\S\S)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+.*', line)
    if m is not None:
        month_name, day, t_hr, t_min, t_sec = m.group(1, 2, 3, 4, 5)
        try:
            month = datetime.strptime(month_name, '%b').month
        except:
            return None
        year = now.year
        if month == 12 and now.month == 1:
            year -= 1
        dt = datetime(*map(int, [year, month, day, t_hr, t_min, t_sec]))
        return dt
    return None

def main(argv):
    goal_hours = 40
    adjustments = get_adjustments()
    parser = argparse.ArgumentParser('hours')
    parser.add_argument('-t', '--total', type=float, metavar='TOT',
            help='set target number of hours for the week')
    parser.add_argument('-a', '--adjust', type=float, metavar='ADJ',
            help="adjust given day's (default today) hours by ADJ")
    parser.add_argument('-d', '--day',
            help='specify which day to adjust hours for with --adj')
    parser.add_argument('-s', '--show-schedule', action='store_true',
            help='show schedule for the rest of the week')
    args = parser.parse_args(argv[1:])
    if args.total is not None:
        goal_hours = args.total
    if args.adjust is not None:
        adj_date = now.strftime(ISO_DATE_FMT)
        if args.day is not None:
            adj_date = get_date_from_day_spec(args.day)
            if adj_date == '':
                sys.stderr.write('Unknown DAY format.\n')
                sys.stderr.write('Specify DAY as an integer or as a day name\n')
                sys.exit(2)
        if adj_date in adjustments:
            adjustments[adj_date] += args.adjust
        else:
            adjustments[adj_date] = args.adjust
        save_adjustments(adjustments)
        sys.stdout.write('Adjusted %s by %.1f\n' % (adj_date, args.adjust))

    times = []
    for i in range(7):
        times.append([None, None])

    f = open(LOG_FILE, 'r')
    for line in iter(f.readline, ''):
        if (re.search(r'gnome-screensaver.*unlocked.login.keyring', line)
                or re.search(r'screen unlocked', line)
                or re.search(r'gdm.session.*session.opened.for.user', line)):
            # found a login line
            dt = get_dt_from_log_line(line)
            idx = (dt.date() - monday).days
            if times[idx][0] is None or dt < times[idx][0]:
                times[idx][0] = dt
        elif re.search(r'lock-screen:', line):
            # found a logout line
            dt = get_dt_from_log_line(line)
            idx = (dt.date() - monday).days
            if times[idx][1] is None or dt > times[idx][1]:
                times[idx][1] = dt
    f.close()

    now_idx = (now.date() - monday).days
    if times[now_idx][1] is None or now > times[now_idx][1]:
        times[now_idx][1] = now

    def fmt_time_dt(dt):
        s = dt.strftime('%I:%M') + dt.strftime('%p')[0].lower()
        if s[0] == '0':
            s = s[1:]
        return s

    border = lambda: sys.stdout.write('-' * 55 + '\n')

    total_hours = 0
    sched_hours = 0
    border()
    for time in times:
        if time[0] is not None:
            sys.stdout.write('%-14s' % (time[0].strftime('%d %A') + ':'))
            sys.stdout.write(fmt_time_dt(time[0]))
            sys.stdout.write(' - ')
            if time[1] is not None:
                sys.stdout.write(fmt_time_dt(time[1]))
                seconds = (time[1] - time[0]).seconds
                hours = round(seconds / 60.0 / 60.0, 1)
                iso_spec = time[1].strftime(ISO_DATE_FMT)
                adj = 0.0
                if iso_spec in adjustments:
                    adj = adjustments[iso_spec]
                hours += adj
                sys.stdout.write(' (%.1f hours)' % hours)
                if adj != 0.0:
                    sys.stdout.write(
                            ' [%s%.1f]' % ('+' if adj > 0.0 else '', adj))
                hours_to_do_today = min(goal_hours - total_hours, HOURS_PER_DAY)
                if (iso_spec == now.strftime(ISO_DATE_FMT)
                        and hours < hours_to_do_today):
                    sched_hours += hours_to_do_today
                    sys.stdout.write(', %.1f at %s' % (hours_to_do_today,
                        fmt_time_dt(time[0] + timedelta(0,
                            (hours_to_do_today - adj) * 60 * 60))))
                else:
                    sched_hours += hours
                total_hours += hours
            else:
                sys.stdout.write('???')
            sys.stdout.write('\n')

    # schedule for remainder of week
    if args.show_schedule:
        d = now.date() + timedelta(1)
        while sched_hours < goal_hours:
            hours_to_do = min(goal_hours - sched_hours, HOURS_PER_DAY)
            sys.stdout.write('%-14s' % (d.strftime('%d %A') + ':'))
            sys.stdout.write('* %.1f hours *' % hours_to_do)
            sys.stdout.write('\n')
            sched_hours += hours_to_do
            d += timedelta(1)

    border()
    sys.stdout.write(
            'at %.1f, goal %.1f, %.1f remain' % (total_hours, goal_hours,
                goal_hours - total_hours))
    sys.stdout.write('\n')

if __name__ == "__main__":
    sys.exit(main(sys.argv))
