#! /usr/bin/env python
"""
Routines for processing a schedule of busy periods by day supplied
either as a string or as a list-of-lists data structure and returning
schedules of both busy and free periods, the latter reflecting the
values of earliest (time), latest (time) and block (minutes).

text data (string):
'''
    d: b-e, b-e, ..., b-e
    d: b-e, b-e, ..., b-e
    ...
    d: b-e, b-e, ..., b-e
'''

E.g.,
txt = '''
    2008-09-01:
    2008-09-02: 8:00a-8:45a, 9:00a-12:00p, 2:45p-4:15p, 5:00p-6:00p
    2008-09-03: 9:00a-1:00p, 1:20p-3:00p, 3:45p-4:45p
    2008-09-04: 8:00a-5:00p
    2008-09-05: 9:45a-10:30, 10:30a-11:30a, 1:00p-4:00p
'''

data structure (list of lists):
    [
        [d, [b,e], [b,e], ... , [b,e]],
        [d, [b,e], [b,e], ... , [b,e]],
        ...
        [d, [b,e], [b,e], ... , [b,e]],
    ]

Each 'd' is a date, each 'b' is a begin time for a busy interval, and
each 'e' is an end time for the interval. Dates are in YYYY-MM-DD format
and times are in HH:MM([aA]|[pP])[mM]? format.
"""

import sys, re, datetime
from dateutil.parser import parse
from textwrap import wrap as text_wrap
from etm.rc import *
from etm.parsers import get_today

cushion = 15
# set these in rc
# busychar = "*"
# freechar = "-"
# conflictchar = "#"
slotsize = 10 

time_regex = re.compile(r'^\s*(\d{1,2}:\d{2})\s*(([ap])m?)?\s*$')
day_regex = re.compile(r'^\s*([\d\-]+):\s*(.*)$')

# for adjusting the hourbar offset
td = get_today().strftime(weekday_fmt)
hourbar_offset = len(td) + 2

def txt2data(txt):
    """
    Process a string with lines in the format:
            date: begin_time - end_time, begin_time - end_time, ...
            date: begin_time - end_time, begin_time - end_time, ...
            ...
            date: begin_time - end_time, begin_time - end_time, ...
    returning a data structure.
    """
    lines = txt.split('\n')
    data = []
    for line in lines:
        m = day_regex.match(line)
        if m:
            date = m.group(1)
            if len(m.groups()) > 1:
                parts = m.group(2).split(',')
                busy = []
                for part in parts:
                    i = part.split('-')
                    if len(i) > 1:
                        interval = [i[0].strip(), i[1].strip()]
                        busy.append(interval)
            else:
                busy = []
            data.append([date.strip(), busy])
    return data


def data2busyfree(earliest, latest, block, slack, slotsize, data, showbars):
    """
    Input a data structure and return busy and free lists, the latter
    reflecting the values of earliest (time), latest (time) and block
    (minutes).
    """
    earliest_minute = t2m(earliest)
    earliest = m2t(earliest_minute)
    starthour = earliest_minute/60
    latest_minute = t2m(latest)
    latest = m2t(latest_minute)
    endhour = latest_minute/60
    if latest_minute%60 > 0:
        endhour += 1
    cols = (endhour - starthour)*(60/slotsize) + 15
    busy_times = []
    free_times = []
    busy_bars = [HourBar(starthour, endhour, slotsize)]
    free_bars = [HourBar(starthour, endhour, slotsize)]
    busyfree_bars = [HourBar(starthour, endhour, slotsize)]
    twdth = cols - 15
    for date, b in data:
        d = parse(date).strftime(weekday_fmt)
        if len(b) > 0:
            try:
                b_times, f_times = _process_busy(earliest, latest, 
                        block, slack, b)
                bbar_str = "".join(MarkList(b_times, earliest, latest,
                    busychar, slotsize))
                s = _l2s(b_times).strip()
                if s:
                    btime_lines = text_wrap("%s" % s, width = twdth)
                else:
                    btime_lines = ['']
                fbar_str = "".join(MarkList(f_times, earliest, latest,
                    freechar, slotsize))
                s = _l2s(f_times).strip()
                if s:
                    ftime_lines = text_wrap("%s" % s, width = twdth)
                else:
                    ftime_lines = ['']
                busy_bars.append("%s: %s" % (d, bbar_str))
                busy_times.append("%s: %s" % (d, btime_lines.pop(0).strip()))
                for line in btime_lines:
                    busy_times.append("%s%s" % (" "*15, line.strip()))
                free_bars.append("%s: %s" % (d, fbar_str))
                free_times.append("%s: %s" % (d, ftime_lines.pop(0).strip()))
                for line in ftime_lines:
                    free_times.append("%s%s" % (" "*15, line.strip()))
            except:
                print "except:", d, earliest, latest, block, b
                print sys.exc_info()
        else:
            busy_times.append("%s: %s" % (d, ""))
            busy_bars.append("%s: %s" % (d,
                "".join(SlotList(starthour, endhour, slotsize))))
            free_times.append("%s: %s" % (d, _l2s([[earliest, latest]])))
            free_bars.append("%s: %s" % (d, "".join(MarkList([[earliest, latest]], earliest, latest, freechar, slotsize))))

    return(busy_bars, busy_times, free_bars, free_times, cols)

def print_output(title, lst):
    l = "-"*len(title)
    print "%s\n%s" % (title, l)
    for line in lst:
        print "  %s" % line
    print ""

def t2m(t):
    """
    Convert time (recognizing format) to minutes after midnight.
    """
    m = time_regex.match(t.lower())
    if not m:
        return None
    g = m.groups()
    if len(g) > 1:
        ap = m.group(3)
    else:
        ap = ''
    (h, m) = map(int, m.group(1).split(':'))
    if ap == 'a' and h == 12:
        return m
    elif ap == 'p' and h < 12:
        return (12+h)*60 + m
    else:
        return h*60 + m

def m2t(minutes):
    """
    Convert minutes after midnight to time in format determined by
    'use_ampm'.
    """
    try:
        minutes = int(minutes)
    except:
        return None
    h = minutes/60
    m = minutes%60
    if use_ampm:
        if h == 0:
            return  "%d:%02da" % (12, m)
        elif h < 12:
            return  "%d:%02da" % (h, m)
        elif h == 12:
            return  "%d:%02dp" % (h, m)
        else:
            return  "%d:%02dp" % (h-12, m)
    else:
        return "%d:%02d" % (h, m)

def _process_busy(earliest, latest, block, slack, busy):
    """
    Convert times in a list of begin-end intervals to minutes
    after midnight and return both busy and free lists, the latter
    reflecting the values of earliest, latest and block.
    """
    f_lst = []
    f_tmp = []
    b_lst = []
    b_tmp = []
    earliest_minute = t2m(earliest)
    latest_minute = t2m(latest)
    ff = earliest_minute  # the first potentially free minute
    if len(busy) > 0:
        for (f, l) in busy:
            fb = t2m(f)  # first busy minute in interval
            lb = t2m(l)  # last busy minute in interval
            b_lst.append([m2t(fb),m2t(lb)])
            lf = min(fb - slack, latest_minute) # last possible free minute
            if ff + block <= lf:
                f_lst.append([m2t(ff), m2t(lf)])
            ff = lb + slack
        if ff + block <= latest_minute:
            f_lst.append([m2t(ff),m2t(latest_minute)])
        return b_lst, f_lst
    else:
        return [], [[m2t(earliest_minute), m2t(lastest_minute)]]

def _l2s(lst):
    """
    Convert a list of time intervals to a string.
    """
    t = []
    for i in lst:
        t.append("%s-%s" % (i[0],i[1]))
    return ", ".join(t)

def SlotList(starthour, endhour, slotsize):
    l = []
    for x in range(starthour*60, endhour*60, slotsize):
        if x%60 == 0:
            l.append('.')
        else:
            l.append(' ')
    l.append('.')
    return l

def HourBar(starthour, endhour, slotsize):
    s = ""
    hour = starthour
    for x in range(starthour, endhour+1):
        if use_ampm and x > 12:
            x -= 12
        h = "%d" % x
        s += ("%-*s" % (60/slotsize, h))
    return "%s%s" % (' '*hourbar_offset, s)

def MarkInterval(slotlist, startminute, endminute, earliest, latest, char, slotsize):
    earliest_minute = t2m(earliest)
    starthour = earliest_minute/60
    latest_minute = t2m(latest)
    endhour = latest_minute/60
    if latest_minute%60 > 0:
        endhour += 1
    startminute = max(int(startminute), earliest_minute)
    endminute = min(int(endminute), latest_minute)
    # starting slot position of interval
    s = (startminute - 60*starthour)/slotsize
    # ending slot position of interval
    m = (endminute - 60*starthour)
    e = m/slotsize
    if m%slotsize > 0:
        e += 1
    for i in range(s,e+1):
        if slotlist[i] not in [' ', '.']:
            slotlist[i] = conflictchar
        else:
            slotlist[i] = char
    return slotlist

def MarkList(list, earliest, latest, char, slotsize):
    earliest_minute = t2m(earliest)
    starthour = earliest_minute/60
    latest_minute = t2m(latest)
    endhour = latest_minute/60
    if latest_minute%60 > 0:
        endhour += 1
    slotlist = SlotList(starthour, endhour, slotsize)
    for (b,e) in list:
        slotlist = MarkInterval(slotlist, t2m(b), t2m(e),
            earliest, latest, char, slotsize)
    return slotlist

if __name__ == "__main__":
    # test values:
    use_ampm = True
    earliest = "8:00a"
    latest = "8:00p"
    slotsize = 5
    txt = """
    2008-09-01:
    2008-09-02: 8:00-8:30, 9:00a-9:45a, 11:00a-12:00p, 1:45p-2:17p, 3:00p-3:52p, 4:08p-4:30p, 5:00p-6:00p
    2008-09-03: 9:00-13:00, 1:20p-3:00p, 3:45p-4:45p
    2008-09-04: 8:00-17:00
    2008-09-05: 9:45a-10:30, 10:30a-11:30a, 13:00-16:00
    """
    data = txt2data(txt)
    for block in [15, 30, 60]:
        bb,bt,fb,ft,cols = data2busyfree(earliest,latest,block, cushion, slotsize, data, True)
        t = "Free periods (bars) of at least %s minutes between %s and %s" % (
            block, earliest, latest)
        print_output(t, fb)
        t = "Free periods (times) of at least %s minutes between %s and %s" % ( block, earliest, latest)
        print_output(t, ft)
    t = "Busy periods (bars)"
    print_output(t, bb)
    t = "Busy periods (times)" 
    print_output(t, bt)
