import sys, datetime, os, os.path, fnmatch, shutil, copy, re, subprocess
import time
from optparse import OptionParser, OptParseError, OptionError, OptionConflictError, BadOptionError, OptionValueError
from dateutil.parser import parse as duparse
from calendar import TextCalendar
from calendar import LocaleTextCalendar
from etm.v import version
from etm.rc import *

import locale
try:
    # try using the user's default settings.
    locale.setlocale(locale.LC_ALL, '')
except:
    # use the current setting for locale.LC_ALL
    locale.setlocale(locale.LC_ALL, None)

etm_redirect = os.path.join(etmdir, 'etm.log')

logging = False
msg = []
has_icalendar = False
# for exporting to .ics
try:
    from icalendar import Calendar, Event, Todo, Journal, FreeBusy, UTC
    has_icalendar = True
except:
    has_icalendar = False

current_day = None
def get_today(td=None):
    """If called with a date and current_day = None, then return current_day on this and subsequent calls until called again with a non-null argument."""
    global current_day
    if current_day:
        if not td:
            return(current_day)
        else:
            current_day = None
            return(datetime.date.today())
    else: 
        if td:
            current_day = parse_date(td)
            return(current_day)
        else:
            return(datetime.date.today())

oneday = datetime.timedelta(days=1)
onesecond = datetime.timedelta(seconds=1)
oneminute = datetime.timedelta(minutes=1)
agenda_dflt = agenda
next_dflt = next
recent_dflt = recent
today = get_today()
lastyear = str(int(today.strftime("%Y"))-1)
thismonth = today.strftime("%m")
thisyear = today.strftime("%Y")
pastdate = today - recent*oneday
nextdate = pastdate + (next-1)*oneday
agendadate = today + (agenda-1)*oneday
verbose_toggle = verbose
date_fmt = "%Y-%m-%d"

monthname_fmt = "%b %d %y"

if use_ampm:
    timefmt = "%I:%M%p"
    datetime_fmt = "%Y-%m-%d %I:%M%p"
else:
    timefmt = "%H:%M"
    datetime_fmt = "%Y-%m-%d %H:%M"

if thisyear == '2009':
    copyright = '2009'
else:
    copyright = '2009-%s' % thisyear


main_color_attrs = {
   -2 : main_dtls,
   -1 : main_mclr,
    0 : main_gclr,
    1 : main_pclr,
    2 : main_tclr,
    3 : main_sclr,
    4 : main_dclr,
    5 : main_hclr,
    6 : main_bclr,
    7 : main_wclr,
    8 : main_nclr,
    # 6 : "#99CC66",
    # 7 : "#9999FF",
}

print_color_attrs = {
   -2 : print_dtls,
   -1 : print_mclr,
    0 : print_gclr,
    1 : print_pclr,
    2 : print_tclr,
    3 : print_sclr,
    4 : print_dclr,
    5 : print_hclr,
    6 : print_bclr,
    7 : print_wclr,
    8 : print_nclr,
}

text_color_attrs = {
   -2 : "#555555",
   -1 : mclr,
    0 : gclr,
    1 : pclr,
    2 : tclr,
    3 : sclr,
    4 : dclr,
    5 : hclr,
    6 : bclr,
    7 : wclr,
    8 : nclr,
}

# use_leader = False

oneday = datetime.timedelta(days=1)
today = get_today()
part_regex = re.compile(r'^(\S)\s*(\S.*)')
etmdata_regex = re.compile(r'%s/?' % etmdata)
tab_regex = re.compile(r'\t')
days_regex = re.compile(r'^\s*([+-]?)\s*(\d+)')
parens_regex = re.compile(r'^\s*\((.*)\)\s*$')
leadingzero = re.compile(r'^0')
embeddedzero = re.compile(r'\s+0')
leadingspaces = re.compile(r'^(\s*)\S.*')
endline_regex = re.compile(r'[\n\r]', re.DOTALL)
done_regex = re.compile(r'.*\d{2}_%s.txt' % (done))
year_regex = re.compile(r'\!(\d{4})\!')
item_regex = re.compile(r'^\s*(\*|\~|\+|\-|\!|\_+|\.+)\s+(\S.*)$')
range_regex = re.compile(r'^(.*\b)(range\([\d\, ]+\))(.*)$')
project_regex = re.compile(r'\s*[^+\-\*\~@\s].*', re.DOTALL)
comment_regex = re.compile(r'\s*#')
lastfield_regex = re.compile(r'.*[-@]([ckr])\s*([^@]*)$')
# to match event lines in the alert queue:
evnt_regex = re.compile(r'\s+[0-9]')
# for the date calculator
calc_days_regex = re.compile(r'^(.+)\s+([-+])\s+(.+)(?=days?)')
# for relative date parsing
rel_date_regex = re.compile(r'^([-+])([0-9]+)')


special_keys = [ 'chrcode', 'do_next', 'type', 'file', 'j', 't']
common_keys = [ 'c', 'd', 'i', 'k', 'l', 'M', 'm', 'r', 'u', 'W', 'w', 'x' ]
project_keys = ['j'] + common_keys + ['b']
task_keys = common_keys + ['o', 'n', 'b', 'f']
event_keys = project_keys + ['s', 'e', 'a', 'n']
no_repeat_keys = ['d', 'b', 'c', 'k', 'n', 'f']
action_keys = ['c', 'd', 'p', 'k', 'n', 'j']
note_keys = ['c', 'd', 's', 'k', 'n', 'j']
all_keys = [ 'a', 'b', 'c', 'd', 'e', 'f', 'i', 'k', 'l', 'M', 'm',
'n', 'o', 'p', 'j', 'r', 's', 'u', 'W', 'w', 'x', ]
sort_keys = [ 'd', 'b', 'p', 's', 'e', 'a', 'c', 'k', 'f', 'r', 'i',
'W', 'w', 'M', 'm', 'x', 'l', 'u', 'o', 'n', ]

alphalist = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

# for checks
date_keys = ['d', 'u', 'f']
list_date_keys = ['l', 'x']
# time_keys = ['s', 'e']
integer_keys = ['b', 'i', 'p']
list_integer_keys = ['a']
repeat_keys = ['l', 'm', 'M', 'u', 'w', 'W', 'x']

second_keys = [
'a', 'n', 'b', 'f', 'r', 'i', 'M', 'm', 'W', 'w', 'o', 'l', 'x' ]
g_keys = {}
# groupby date:
g_keys['d'] = ['c', 'j', 'k'] + second_keys 
# groupby context:
g_keys['c'] = ['j', 'k'] + second_keys 
# groupby keyword:
g_keys['k'] = ['c', 'j'] + second_keys 
# groupby project
g_keys['j'] = ['c', 'k'] + second_keys 

def sysinfo():
    from platform import python_version as pv
    from dateutil import __version__ as dv
    try:
        import wx
        wxv = "%s.%s.%s" % (wx.MAJOR_VERSION, 
                wx.MINOR_VERSION, wx.RELEASE_VERSION)
    except:
        wxv = "none"
    sysinfo = "platform: %s; python %s; dateutil %s; wx(Python) %s" % (sys.platform, pv(), dv, wxv)
    return(sysinfo)
    
def etminfo():
    etminfo = "etmrc: %s; etmdata: %s" % (etmrc, etmdata)
    return(etminfo)

def newer():
    from urllib import urlopen, urlretrieve
    try:
        vstr = urlopen(
            "http://www.duke.edu/~dgraham/ETM/version.txt").read().strip()
        if int(version) < int(vstr): 
            return('A newer release, etm %s, is available.' % (vstr))
        else:
            return('etm %s is the newest version available.' % 
                    (vstr))

    except:
        return('Could not connect. Please try again later. %s')

dflts = {
    'j':   task,       # project title
    'c':   context,    # context
}

frequency_names = {
    'd' : 'DAILY',
    'w' : 'WEEKLY',
    'm' : 'MONTHLY',
    'y' : 'YEARLY',
    'l' : 'LIST'
}

by_names = {
    'w' : 'weekday',
    'W' : 'week',
    'm' : 'monthday',
    'M' : 'month'
}

### Help text ###

date_event_text = [
'    date         @d a date. Required unless event is list-only repeating',
]

date_task_text = [
'    date         @d a date. Required for repeating tasks.',
]

common_text = [
'    context      @c string',
'    keywords     @k string or list of strings',
'    repeat       @r [dwmyl]: d)aily, w)eekly, m)onthly, y)early, l)ist',
'      interval   @i a positive integer. Default: 1',
'      until      @u a date. Default: forever',
'      weekday    @w MO, TU, ..., MO(-1) or [0-6] with 0=MO,',
'                 or a list of weekdays or weekday numbers',
'      week       @W an integer week number or list of week numbers',
'      monthday   @m an integer month day or list of monthdays',
'      month      @M an integer month number or list of month numbers',
'      include    @l a date or a list of (non-matching) dates to include',
'      exclude    @x a date or a list of (matching) dates to exclude',
]

list_text = """\
All dates are in YYYY-MM-DD format and all times in HH:MM[AP] format.
Fuzzy parsing is supported for both dates and times in all cases. All
lists must be comma separated and enclosed in parentheses. A list of
numbers can also be specified using the range operator, e.g., range(1,5)
instead of (1,2,3,4) or range(5,20,3) instead of (5,8,11,14,17).
""".split('\n')

textpad_keys = [
'Return an empty entry or an entry beginning with a period to cancel.',
'Editing keys:',
'    ^A: move to beginning of line        ^E: move to end of line ',
'    ^D: delete character under cursor    ^H: delete character backwards',
'    Return or ^G: process entry          ^K: delete to end of line',
'',
'Entry details:'
]

task_text = [
'[._+-] &lt;task name&gt; [Options]',
'       where',
'           one or more \'.\' or \'_\': a task in outline format',
'           \'+\': a parallel task; \'-\': a series task;',
'       Note: the use of \'+\' and \'-\' is depreciated.',
'Options:',
] + date_task_text + common_text + [
'      overdue    @o k)eep, r)estart, s)kip. Default: k',
'    note         @n string',
'    begin        @b integer number of days before @d date',
'    finished     @f a date',
] # + list_text

project_text =['&lt;project name&gt; [Options]',
'    Options below become the default for each of the following',
'    events, tasks and actions.',
'Options:'] + common_text # + list_text

event_text =['* &lt;event name&gt; [Options]',
'Options:',
] + date_event_text + common_text + [
'    starttime    @s a time',
'    endtime      @e a time or an integer number of minutes after',
'                 starttime. If starttime is set, the default is to',
'                 set endtime %s (EXTENT) minutes later.' % extent,
'    alerts       @a an integer or a list of integer minutes before',
'                 starttime',
'    note         @n string',
] # + list_text

note_text =['! &lt;the text of the note&gt; [Options]',
'Options:',
] + [
"    date         @d a date. Required. The current date will be used if",
'                 one is not provided',
'    starttime    @s a time. Required. The current time will be used if',
'                 one is not provided',
'    context      @c string',
'    keywords     @k string or list of strings',
'    note         @n string (only shown in the verbose display)',
] 

action_text =['~ &lt;Description of action&gt; [Options]',
'Options:',
'    date         @d a date. Required.',
'    period       @p elapsed time in minutes. Required.',
'    context      @c string.',
'    keywords     @k string or list of strings. Keywords containing',
'                 colon(s), e.g., "x:y", "x:z", will be split to form',
'                 groups and subgroups when aggregating times.',
] # + list_text

usage = """\
usage: ...
"""

help = """\
etm (Event and Task Manager) version %s
Copyright %s Daniel A Graham. All rights reserved.
%s
%s

Information:
    h       Show this help message
    c       Prompt for a date expression of the form 'date (+-) string'
            where 'string' is either a date or an integer followed
            by the word 'days' and return the result. E.g., 'dec 1 +
            90 days' or 'nov 30 - sep 1'. Note: '+' cannot be used
            when 'string' is a date.
    N       Display the latest available version of etm
    p       Enter a persistent loop in which the other commands listed
            here can be repeated without reloading the etm data files.
    w       Start the wx(Python) based gui version of etm. If the 
            current working directory contains a file named 'emtrc'
            then settings from that file will be used instead of those
            from '~/.etm/etmrc'.

Displays:
    b [B]   Display busy/free times using command line options [first
            prompting for options and displaying help information].
    l [L]   Display a list of events, actions and tasks using command line 
            options [first prompting for options and displaying help
            information].
    r [R]   Display a reckoning of time spent in events and actions 
            using command line options [first prompting for options and
            displaying help information].
    a [A]   Display the agenda using command line options [first prompting
            for options and displaying help information].
""" % (version, copyright, sysinfo(), etminfo())

guihelp = """%s
Changes:
    i  	    Start the timer for a new action, pause a running timer or
            restart a paused timer. Pressing 'I' stops the timer, prompts
            for a file number to record the entry and prompts for
            modifications of the entry.
    e  	    Create a new event. Prompt for file number, display event help
       	    and prompt for entry.
    n  	    Create a new note. Prompt for file number, display note help
       	    and prompt for entry.
    t  	    Create a new task. Prompt for file number, display task help
       	    and prompt for entry.
    p  	    Create a new project. Prompt for file name, display project
       	    help and prompt for entry.
    d  	    Delete an existing task, event, action or action. Prompt for 
       	    the item number. Warning. This cannot be undone!
    f  	    Mark a task finished. Prompt for task number and, optionally, 
            a (fuzzy parsed) completion date to use instead of today's date.
            For a finished, non-repeating task, remove the finish date. For
            a repeating task, this action cannot be undone.
    m  	    Modify an existing task, event or action. Prompt for the number.
    M  	    Modify an existing task, event or action using the external 
            editor. Prompt for the number.
    o  	    Open an existing project for editing using an external editor.
            Display a numbered list of existing projects and prompt for the
            file number to use.
""" % help
entry_html = """
<br>
<b>
    <em>&lt;Esc&gt; cancels entry;  &lt;Return&gt; accepts entry.
    <br>
    Press TAB to complete @c (context), @k (keyword) or @r (repetition); F5 to select a date;   Up-Arrow to select a template. 
</b>
"""

date_html="""\
<font size="+1"><b>date, time and list details</b></font>
<pre>
%s
</pre>
""" % "\n".join(list_text)

project_html=["<title>project options</title>",
'<body text="%s" bgcolor="%s">' % (main_fgcolor, main_bgcolor), 
'<font size="+1"><b>The project line format</b></font>',
entry_html,
"""\
<pre>
%s
</pre>
</body>
""" % "\n".join(project_text)]

action_html= ["<title>action options</title>",
'<body text="%s" bgcolor="%s">' % (main_fgcolor, main_bgcolor), 
'<font size="+1"><b>The format for action lines</b></font>',
entry_html,
"""\
<pre>
%s
</pre>
</body>
""" % "\n".join(action_text)]

event_html=["<title>event options</title>",
'<body text="%s" bgcolor="%s">' % (main_fgcolor, main_bgcolor), 
'<font size="+1"><b>The format for event lines</b></font>',
entry_html,
"""
<pre>
%s
</pre>
""" % "\n".join(event_text)]

note_html=["<title>note options</title>",
'<body text="%s" bgcolor="%s">' % (main_fgcolor, main_bgcolor), 
'<font size="+1"><b>The format for note lines</b></font>',
entry_html,
"""
<pre>
%s
</pre>
""" % "\n".join(note_text)]

task_html=["<title>task options</title>",
'<body text="%s" bgcolor="%s">' % (main_fgcolor, main_bgcolor), 
'<font size="+1"><b>The format for task lines</b></font>',
entry_html,
"""\
<pre>
%s
</pre>
</body>
""" % "\n".join(task_text)]

help_text = """\
        <title>etm: event and task manager</title>
<center>
Copyright %s Daniel A Graham. All rights reserved.
<br>
%s
<br>
%s
</center>
<pre>
<b>General</b>
   Ctrl-Q   Quit etm.
   Ctrl-P   Print the current display.
   Ctrl-S   Save the current agenda, list, busy/free or reckoning view
            as formatted text to the system clipboard.
   Escape   Cancel the current action.

<b>Showing information:</b>
    F1      Toggle displaying this help message.
    F2      Toggle displaying a twelve month calendar starting
            with the previous month.
    F3      Force reloading of event, task and action data.
    F4      Prompt for a date expression of the form 'date (+-) string'
            where 'string' is either a date or an integer followed
            by the word 'days' and return the result. E.g., 'dec 1 +
            90 days' or 'nov 30 - sep 1'. Note: '+' cannot be used
            when 'string' is a date.
    c       Copy an existing task to the system clipboard. Prompt for
            the number.
    q       Show the status of the background alert warnings queue.
    N       Check for a later version of etm.
    w       Show current Yahoo weather data.
    d       Show current USNO sun and moon data.

<b>Creating new entries and changing existing ones</b>
    e:  Create a new event. Prompt for file number, display event 
        help and prompt for entry.
    n:  Create a new note. Prompt for file number, display note 
        help and prompt for entry.    
    t:  Create a new task. Prompt for file number, display task help
        and prompt for entry.
    i:  Start the interval timer for a new action first prompting for an
        initial entry for the action. Pressing 'i' again pauses a running
        timer or restarts a paused timer. Pressing 'I' stops the timer,
        prompts for a file number to record the entry, shows action help
        information and prompts for the completion of an entry that
        already contains the date and elapsed time.
    j:  Create a new project. Prompt for file name, display project 
        help and prompt for entry.
    f:  Mark a task finished. Prompt for the task number. If an argument
        is provided it will be (fuzzy) parsed for the completion date.
        Otherwise TODAY (today's date) will be used. 
    u:  Mark a task unfinished.
    m:  Modify an existing task, event or action. Prompt for the 
        number.
    M:  Modify an existing task, event or action using the external 
        editor. Prompt for the number.
    J:  Open an existing project for editing. Display a numbered list 
        of existing projects and prompt for the file number to use.
    D:  Delete an existing task, event, action or action. Prompt for
        the item number. Warning. This cannot be undone!

    Note: After pressing f, u, m or D, you will need to enter the id of 
          the relevant item. This id is a string of from 1 to 3 lower
          case letters followed by ')' that will be prepended to the
          title of the item after pressing one of these keys.

<b>Viewing tasks, events and actions</b>
    a  A    (or space) Display the agenda view using default values (a,
            space) or prompting for options and showing detailed agenda
            usage information (A).
    b  B    Display the busy/free times view using default values (b) 
            or prompting for options and detailed showing busy/free 
            usage information (B).
    l  L    Display the list view of events, actions and tasks using 
            default values (l) or prompting for options and showing 
            detailed list usage information (L).
    r  R    Display the reckoning view of time spent in events and 
            actions using default values (r) or prompting for options
            and showing detailed reckoning usage information (R).
    v       Toggle verbose display in agenda and list views.
    .       (or Right-Arrow, &gt;) Shift the period displayed forward by
            the number of days currently being displayed. [Not available
            in agenda view.]
    ,       (or Left-Arrow, &lt;) Shift the period displayed backward by
            the number of days currently being displayed. [Not available
            in agenda view.]
    /       Reset the period displayed to that initially displayed. [Not
            available in agenda view.]

<b>Entry bar editing keys</b>
    &lt;Esc&gt;: cancel the entry dialog. 
    &lt;Return&gt;: accept the entry and end the dialog. 

    
    Press 
        TAB after entering either "-" or "@" followed by
            c: to select a context from a list of previously used contexts.
            k: to select a keyword from a list of previously used keywords.
            r: to select a repetition from a list of previously used repetitions.

        Up-Arrow: to select options from a list of previously
                  used option strings (only when entering options for agenda,
                  list, busy and reckoning views).

        F5: to select a date from a calendar dialog. 

    The selection will be inserted in the entry bar at the current 
    cursor location. 
</pre>
""" % (copyright, sysinfo(), etminfo())

agendaview_html = """
<title>etm</title>
<body text="%s" bgcolor="%s"> 
<font size="+1"><b>agenda view</b></font>
<br>
<em>&lt;Esc&gt; cancels entry; &lt;Return&gt; accepts entry.<br>
Press TAB to complete @c (context) or @k (keyword); F5 to select a date; Up-Arrow to select an option string from history list.</em>
<pre>
<b>options:</b>
  -d DAYS  Positive integer. The number of days to display. Default:
           %s.
  -e END   Date. Display tasks/events beginning with BEGIN and ending 
           with this date (fuzzy parsed). Default: %s.
  -g GROUPBY  An element from [d,p,c,k] where:
              d: group by date
              j: group by project
              c: group by context
              k: group by keyword
              Default: d.
  -f FIND     Regular expression. Show items containing FIND (ignoring
              case) in the task title or note within the BEGIN ~ END
              interval.
  -c CONTEXT  Regular expression. Show items with contexts matching 
              CONTEXT (ignoring case) within the BEGIN ~ END interval.
  -j PROJECT  Regular expression. Show items with projects matching 
              PROJECT (ignoring case) within the BEGIN ~ END interval.
  -k KEYWORD  Regular expression. Show items with contexts matching 
              KEYWORD (ignoring case) within the BEGIN ~ END interval.
  -s SHOW     String. Show 
              a: actions 
              b: begin task dates         
              e: events
              f: finished tasks 
              n: notes
              t: available tasks 
              w: waiting tasks 
              depending upon whether SHOW contains 'a', 'b', 'e', 'f',
              'n', 't' and/or 'w'. Default: %s.
</pre>
</body>
""" % (main_fgcolor, main_bgcolor, agenda, agendadate, agenda_show)

listview_html = """
<title>etm</title>
<body text="%s" bgcolor="%s">
<font size="+1">List view</font>
<br>
<em>&lt;Esc&gt; cancels entry; &lt;Return&gt; accepts entry.<br>
Press TAB to complete -c (context) or -k (keyword); F5 to select a date; Up-Arrow to select an option string from history list.</em>
<pre>
<b>options:</b>
  -d DAYS     Positive integer. The number of days to display. 
              Default: %s (NEXT).
  -b BEGIN    Date. Display tasks/events beginning with this date 
              (fuzzy parsed) and including DAYS days. Default:
              %s (TODAY - RECENT days).
  -e END      Date. Display tasks/events beginning with BEGIN and 
              ending with this date (fuzzy parsed). Default: 
              %s (BEGIN + (DAYS-1) days).
  -g GROUPBY  An element from [d,p,c,k] where:
              d: group by date
              j: group by project
              c: group by context
              k: group by keyword
              Default: d.
  -f FIND     Regular expression. Show items containing FIND (ignoring
              case) in the task title or note within the BEGIN ~ END
              interval.
  -c CONTEXT  Regular expression. Show items with contexts matching 
              CONTEXT (ignoring case) within the BEGIN ~ END interval.
  -j PROJECT  Regular expression. Show items with projects matching 
              PROJECT (ignoring case) within the BEGIN ~ END interval.
  -k KEYWORD  Regular expression. Show items with contexts matching 
              KEYWORD (ignoring case) within the BEGIN ~ END interval.
  -s SHOW     String. Show 
              a: actions 
              b: begin task dates         
              e: events
              f: finished tasks 
              n: notes
              t: available tasks 
              w: waiting tasks 
              depending upon whether SHOW contains 'a', 'b', 'e', 'f',
              'n', 't' and/or 'w'. Default: %s.
  -x EXPORT   Export list view items in iCal format to file EXPORT.ics
              in %s.
</pre>
</body>
""" % (main_fgcolor, main_bgcolor, next, pastdate, nextdate, list_show, etmical)

busyview_html = """
<body text="%s" bgcolor="%s">
<font size="+1">Busy/free view</font>
<br><em>&lt;Esc&gt; cancels entry; &lt;Return&gt; accepts entry.<br>
Press TAB to complete -c (context) or -k (keyword); F5 to select a date; Up-Arrow to select an option string from history list.</em>
<pre>
<b>options:</b>
  -d DAYS     Positive integer. The number of days to display. 
              Default: %s (NEXT).
  -b BEGIN    Date. Display tasks/events beginning with this date 
              (fuzzy parsed) and including DAYS days. Default:
              %s (TODAY - RECENT days).
  -e END      Date. Display tasks/events beginning with BEGIN and 
              ending with this date (fuzzy parsed). Default: 
              %s (BEGIN + (DAYS-1) days).
  -f FIND     Regular expression. Show items containing FIND (ignoring
              case) in the task title or note within the BEGIN ~ END
              interval.
  -c CONTEXT  Regular expression. Show items with contexts matching 
              CONTEXT (ignoring case) within the BEGIN ~ END interval.
  -j PROJECT  Regular expression. Show items with projects matching 
              PROJECT (ignoring case) within the BEGIN ~ END interval.
  -k KEYWORD  Regular expression. Show items with contexts matching 
              KEYWORD (ignoring case) within the BEGIN ~ END interval.
  -i INCLUDE  String containing one or more of the letters 'b' 
              (include busy time bars, 'B' (include busy times), 'f'
              (include free time bars) and/or 'F' (include free
              times). Default: %s.
  -s SLACK    Positive integer. Skip SLACK minutes before and after
              busy periods when computing free periods. Default: %s.
  -m MINIMUM  Positive integer. The minimum length in minutes for an
              unscheduled period to be displayed. Default:
              %s.
  -O OPENING  Time. The opening or earliest time (fuzzy parsed) to be
              considered when displaying unscheduled periods. 
              Default: %s.
  -C CLOSING  Time. The closing or latest time (fuzzy parsed) to be
              considered when displaying unscheduled periods. Default:
              %s.
  -S SLOTSIZE Integer. The number of minutes covered by one character
              in the time bar. It must be the case that 60 %% SLOTSIZE
              = 0. Recommended choices are 10, 12 or 15. Default: %s.

</pre>
</body>
""" % (main_fgcolor, main_bgcolor, next, pastdate, nextdate, include, slack, minimum, opening, closing, slotsize)

reckoningview_html = """
<title>etm</title>
<body text="%s" bgcolor="%s">
<font size="+1">Reckoning view</font>
<br>
<em>&lt;Esc&gt; cancels entry; &lt;Return&gt; accepts entry.<br>
Press TAB to complete -c (context) or -k (keyword); F5 to select a date; Up-Arrow to select an option string from history list.</em>
<pre>
<b>options:</b>
  -d DAYS     Positive integer. The number of days to display. 
              Default: %s (NEXT).
  -b BEGIN    Date. Display tasks/events beginning with this date 
              (fuzzy parsed) and including DAYS days. Default:
              %s (TODAY - RECENT days).
  -e END      Date. Display tasks/events beginning with BEGIN and 
              ending with this date (fuzzy parsed). Default: 
              %s (BEGIN + (DAYS-1) days).
  -f FIND     Regular expression. Show items containing FIND (ignoring
              case) in the task title or note within the BEGIN ~ END
              interval.
  -c CONTEXT  Regular expression. Show items with contexts matching 
              CONTEXT (ignoring case) within the BEGIN ~ END interval.
  -j PROJECT  Regular expression. Show items with projects matching 
              PROJECT (ignoring case) within the BEGIN ~ END interval.
  -k KEYWORD  Regular expression. Show items with contexts matching 
              KEYWORD (ignoring case) within the BEGIN ~ END interval.
  -s SHOW     String. Show 
              e: events
              a: actions 
              depending upon whether SHOW contains 'e' and/or 'a'. 
              Default: ea.
  -C C_POSITION  Integer. Subtotal by category if C_POSITION is 
                 positive and in an order corresponding to C_POSITION.
                 Default 0.
  -D D_POSITION  Integer. Subtotal by date if D_POSITION is positive
                 and in an order corresponding to D_POSITION. 
                 Default 1.
  -K K_LEVEL     Integer. Subtotal by keywords to a depth corresponding
                 to the value of K_LEVEL if positive. E.g., if K_LEVEL
                 is 2, and there is an item with keyword 'a:b:c', then
                 subtotals corresponding to 'a' and 'a:b' would be
                 formed. Default 2.
  -I             True/False. Subtotal by title if True. Default: False.
</pre>
</body>
""" % (main_fgcolor, main_bgcolor, next, pastdate, nextdate)

help_html="""\
<title>etm</title>
<body text="%%s" bgcolor="%%s">
%s
<font size="+2">Project files</font>
<p />
Each file in
<pre>
    '%s'
</pre>
('etmdata' in ~/.etm/etmrc) that has the suffix '<tt>txt</tt>' is treated
as a separate project and must begin with a line having the following
format and be followed by task, event and action lines. All tasks, events
and actions used by etm are contained in these project files.
<p />
%s
%s
%s
%s
%s
</body>
""" % (help_text, etmdata, project_html[2]+project_html[4], task_html[2]+task_html[4], event_html[2]+event_html[4], action_html[2]+action_html[4]+note_html[2]+note_html[4], date_html)

### Parsers ###
class ETMOptParser(OptionParser):
    def error(self, m):
        global msg
        msg.append(m)

    def print_help(self, file=None):
        global msg
        msg.append(self.format_help())

bhelp =  """Date. Display tasks/events beginning with this date (fuzzy parsed) and continuing for the next DAYS days. Default: %default.
"""

dhelp = """Positive integer. The number of days to display. Default: %default.
"""

ehelp = """Date. Display tasks/events beginning with BEGIN and ending with this date (fuzzy parsed). Default: %default.
"""

### aparser ###
aparser = ETMOptParser(usage = '')
aparser.add_option("-d", action = "store",
    dest='days', type = int, default=agenda,
    help = dhelp)
aparser.add_option("-e", action = "store",
    dest='end', default=get_today()+(int(agenda)-1)*oneday,
    help = ehelp)
aparser.add_option("-g", action = "store",
    dest='groupby', default = 'd', choices = ['d', 'j', 'c', 'k'],
    help = """An element from [d,p,c,k] where:	                      \n
    d: group by date                        						  \n
    j: group by project				                				  \n
    c: group by context								                  \n
    k: group by keyword								                  \n
    Default: %default.""")
aparser.add_option("-f", 
    action="store",
    dest='find',
    help = """Regular expression. Include items containing FIND (ignoring case) in the task title or note within the BEGIN ~ END interval.""")
aparser.add_option("-c", action = "store",
    dest='context',
    help = """Regular expression. Include items with contexts matching CONTEXT (ignoring case) within the BEGIN ~ END interval.""")
aparser.add_option("-j", action = "store",
    dest='project',
    help = """Regular expression. Include items with projects matching PROJECT (ignoring case) within the BEGIN ~ END interval.""")
aparser.add_option("-k", action = "store",
    dest='keyword',
    help = """Regular expression. Include items with contexts matching KEYWORD (ignoring case) within the BEGIN ~ END interval.""")
aparser.add_option("-s", action = "store",
    dest='show', default=agenda_show,
    help = """String. Include events, actions, available tasks, waiting tasks finished tasks and/or notes depending upon whether SHOW contains 'e', 'a', 't', 'f', 'w' and/or 'n'. Using 't' displays tasks which are available (unfinished with no unfinished prerequisites). Add 'f' to see finished tasks and/or 'w' to see waiting tasks (unfinished with unfinished prerequisites). Default: %default.""")
aparser.add_option("-v", action = "store_true",
    dest='toggle_verbose',
    help = """Toggle displaying item details.""")

### lparser ###
lparser = ETMOptParser(usage = '')
lparser.add_option("-d", action = "store",
    dest='days', type = int, default=next,
    help = dhelp)
lparser.add_option("-b",  dest="begin",
    action="store", default=get_today()-int(recent)*oneday,
    help = bhelp)
lparser.add_option("-e", action = "store",
    dest='end', default=get_today() + (int(next) - int(recent) - 1)*oneday,
    help = ehelp)
lparser.add_option("-g", action = "store",
    dest='groupby', default = 'd', choices = ['d', 'j', 'c', 'k'],
    help = """An element from [d,p,c,k] where:	                      \n
    d: group by date                        						  \n
    j: group by project				                				  \n
    c: group by context								                  \n
    k: group by keyword								                  \n
    Default: %default.""")
lparser.add_option("-f", 
    action="store",
    dest='find',
    help = """Regular expression. Include items containing FIND (ignoring case) in the task title or note within the BEGIN ~ END interval.""")
lparser.add_option("-c", action = "store",
    dest='context',
    help = """Regular expression. Include items with contexts matching CONTEXT (ignoring case) within the BEGIN ~ END interval.""")
lparser.add_option("-j", action = "store",
    dest='project',
    help = """Regular expression. Include items with projects matching PROJECT (ignoring case) within the BEGIN ~ END interval.""")
lparser.add_option("-k", action = "store",
    dest='keyword',
    help = """Regular expression. Include items with contexts matching KEYWORD (ignoring case) within the BEGIN ~ END interval.""")
lparser.add_option("-s", action = "store",
    dest='show', default=list_show,
    help = """String. Include events, actions, available tasks, waiting tasks finished tasks and/or notes depending upon whether SHOW contains 'e', 'a', 't', 'f', 'w' and/or 'n'. Using 't' displays tasks which are available (unfinished with no unfinished prerequisites). Add 'f' to see finished tasks and/or 'w' to see waiting tasks (unfinished with unfinished prerequisites). Default: %default.""")
lparser.add_option("-v", action = "store_true",
    dest='toggle_verbose',
    help = """Toggle displaying item details.""")
lparser.add_option("-x", action = "store",
    dest='export',
    help = """Export list view items in iCal format to file EXPORT.ics in %s.""" % etmical)

### bparser ###
bparser = ETMOptParser(usage = '')
bparser.add_option("-d", action = "store",
    dest='days', type = int, default=next, 
    help = dhelp)
bparser.add_option("-b",  dest="begin",
    action="store", default=pastdate,
    help = bhelp )
bparser.add_option("-e", action = "store",
    dest='end', default=nextdate,
    help = ehelp)
bparser.add_option("-f", 
    action="store",
    dest='find',
    help = """Regular expression. Include items containing FIND (ignoring case) in the task title or note within the BEGIN ~ END interval.""")
bparser.add_option("-c", action = "store",
    dest='context',
    help = """Regular expression. Include items with contexts matching CONTEXT (ignoring case) within the BEGIN ~ END interval.""")
bparser.add_option("-j", action = "store",
    dest='project',
    help = """Regular expression. Include items with projects matching PROJECT (ignoring case) within the BEGIN ~ END interval.""")
bparser.add_option("-k", action = "store",
    dest='keyword',
    help = """Regular expression. Include items with contexts matching KEYWORD (ignoring case) within the BEGIN ~ END interval.""")
bparser.add_option("-s", action = "store",
    dest='slack',  type = int, default=slack,
    help = """Positive integer. Provide a buffer of SLACK minutes before and after busy periods when computing free periods. Default: %default.""")
bparser.add_option("-m", action = "store",
    dest='minimum',  type = int, default=minimum,
    help = """Positive integer. The minimum length in minutes for an unscheduled period to be displayed. Default: %default.""")
bparser.add_option("-S", action = "store",
    dest='slotsize',  type = int, default=slotsize,
    help = """Positive integer. The number of minutes covered by a single character in a time bar. It must be the case that 60 %% slotsize = 0. Recommended choices are 10, 12 or 15 Default: %default.""")
bparser.add_option("-O",  action = "store",
    dest='opening', default=opening,
    help = """Time. The opening or earliest time (fuzzy parsed) to be considered when displaying unscheduled periods. Default: %default.""")
bparser.add_option("-C", action = "store",
    dest='closing', default = closing,
    help = """Time. The closing or latest time (fuzzy parsed) to be considered when displaying unscheduled periods. Default: %default.""")
bparser.add_option("-i", action = "store",
    dest='include', default = include,
    help = """String containing one or more of the letters 'b' (include busy time bars, 'B' (include busy times), 'f' (include free time bars) and/or 'F' (include free times). Default: %default.""")

### rparser ###
rparser = ETMOptParser(usage = '')
rparser.add_option("-d", action = "store",
    dest='days', type = int, default = next,
    help = dhelp)
rparser.add_option("-b",  dest="begin",
    action="store", default=pastdate,
    help = bhelp )
rparser.add_option("-e", action = "store",
    dest='end', default=nextdate,
    help = ehelp)
rparser.add_option("-f", 
    action="store",
    dest='find',
    help = """Regular expression. Include items containing FIND (ignoring case) in the task title or note within the BEGIN ~ END interval.""")
rparser.add_option("-c", action = "store",
    dest='context',
    help = """Regular expression. Include items with contexts matching CONTEXT (ignoring case) within the BEGIN ~ END interval.""")
rparser.add_option("-j", action = "store",
    dest='project',
    help = """Regular expression. Include items with projects matching PROJECT (ignoring case) within the BEGIN ~ END interval.""")
rparser.add_option("-k", action = "store",
    dest='keyword',
    help = """Regular expression. Include items with contexts matching KEYWORD (ignoring case) within the BEGIN ~ END interval.""")

rparser.add_option("-C", action = "store",
    dest='c_position', type=int, default = c_position,
    help = """Integer. Subtotal by category if C_POSITION is positive and in an order corresponding to C_POSITION. Default %default.""")
rparser.add_option("-D", action = "store",
    dest='d_position', type=int, default = d_position,
    help = """Integer. Subtotal by date if D_POSITION is positive and in an order corresponding to D_POSITION. Default %default.""")
rparser.add_option("-K", action = "store",
    dest='k_level', type=int, default = k_level,
    help = """Integer. Subtotal by keywords to a depth corresponding to the value of K_LEVEL if positive. E.g., if K_LEVEL is 2, and there is an item with keyword \'a:b:c\', then subtotals corresponding to \'a\' and \'a:b\' would be formed. Default %default.""")
rparser.add_option("-I", action = "store_true", dest='itemize',
    help = """True/False. Subtotal by title if True. Default: False.""")
rparser.add_option("-s", action = "store",
    dest='show', default='ea',
    help = """String. Include events and/or actions depending upon whether SHOW contains 'e' and/or 'a'. Default: %default.""")

parserHash = {
        'a' : aparser,
        'l' : lparser,
        'b' : bparser,
        'r' : rparser,
        }

nameHash = {
        'a' : 'agenda options',
        'l' : 'list options',
        'b' : 'busy/free options',
        'r' : 'reckoning options'
        }


if logging:
    etmlog = os.path.join(etmdir, 'logging.log')
    #  clear the log
    fo = open(etmlog, 'w')
    fo.close()

def logmsg(msg):
    if logging:
        fo = open(etmlog, 'a')
        fo.write("%s\n" % str(msg))
        fo.close()
    else:
        pass

def parse(str):
    return duparse(str, dayfirst=False, yearfirst=False)

def num2alpha(integer):
    """Convert 1, 2, 3, ..., 27, 28, 29 ... 18278 to a, b, c, ..., aa, ab,
    ac ... zzz. Three alphabetic 'digits' thus allow for intgers from 1
    through  26 + 26^2 + 26^3 = 18,278. Note: repetitions of an item use
    the same id number since all correspond to the same file and linenumber.
    """
    num = int(integer)
    al = []
    while 1:
        al.insert(0, alphalist[num % 26 - 1])
        num = int(num)/26
        if num == 0:
            break
    return ''.join(al)

def m2h(m):
    """
    Return hours and minutes if hours_minutes is true and otherwise hours
    and tenths.
    """
    m = int(m)
    if hours_minutes:
        return "%d:%02d" % (m/60, m%60)
    else:
        if m%6 > 0:
            return "%d.%dh" % (m/60, (m%60+6)/6)
        else:
            return "%d.%dh" % (m/60, (m%60)/6)

def l2u(l_date, l_time="0:00"):
    """Make sure that the dst flag is -1 -- this tells mktime to take daylight
    savings into account"""
    l_dto = parse("%s %s" % (l_date, l_time))
    l_dts = l_dto.timetuple()
    l_secs = time.mktime(l_dts)
    u = time.gmtime(l_secs)
    if l_time != "0:00":
        return datetime.datetime(u.tm_year, u.tm_mon, u.tm_mday, u.tm_hour, u.tm_min, u.tm_sec,
            tzinfo=UTC)
    else:
        return datetime.date(u.tm_year, u.tm_mon, u.tm_mday)

def has(hash, key):
    "Return true if key in hash and hash[key] != None"
    #  return(key in hash and hash[key] != None)
    return(key in hash and hash[key])

def cal(advance=0):
    c = LocaleTextCalendar(week_begin, '')
    cal = []
    y = int(today.strftime("%Y"))
    m = 1
    # m = int(today.strftime("%m"))
    # if m == 1:
    #     m = 12
    #     y -= 1
    # else:
    #     m -= 1
    if advance != 0:
        y += advance
    for i in range(12):
        cal.append(c.formatmonth(y,m).split('\n'))
        m += 1
        if m > 12:
            y += 1
            m = 1
    s = []
    for r in range(0,12,3):
        l = max(len(cal[r]), len(cal[r+1]), len(cal[r+2]))
        for i in range(3):
            if len(cal[r+i]) < l:
                for j in range(len(cal[r+i]), l+1):
                    cal[r+i].append('')
        for j in range(l):
            s.append(("  %-20s    %-20s    %-20s" %
                (cal[r][j], cal[r+1][j], cal[r+2][j])).encode('utf8'))
    return s

def format_date(date, fmt):
    global msg
    if type(date) is str:
        try:
            d = parse(date).date()
            return d.strftime(fmt)
        except:
            msg.append("Could not format date '%s'" % date)
            return(date)
    else:
        return(date.strftime(fmt))

def parse_date(date):
    # return datetime.date
    global msg
    try:
        if type(date) in [str, unicode, int]:
            m = rel_date_regex.match(date)
            if m:
                if m.group(1) == '+':
                    date = get_today() + int(m.group(2))*oneday
                elif m.group(1) == '-':
                    date = get_today() - int(m.group(2))*oneday
            else:
                date = parse('%s' % date)
        if type(date) is datetime.datetime:
            return(date.date())
        elif type(date) is datetime.date:
            return(date)
        else:
            return(today)
    except:
        print('parse_date exception', type(date), date)
        msg.append("Could not parse date '%s'" % date)
        return(today)

def date_calculator(s):
    """process a date expression
    date - date     return days between dates
    date + date     return error
    date - n day(s) return date
    date + n day(s) return date
    """
    st = s.strip()
    time = datetime.time(0,0,0,0)
    m = calc_days_regex.match(st)
    if m:
        # using days
        #  print('days', str,  m.groups())
        a = m.group(1).strip()
        v = m.group(2).strip()
        n = m.group(3).strip()
        days = int(n)
        try:
            a_datetime = datetime.datetime.combine(parse_date(a),time)
        except:
            return(
                "error processing '%s': could not parse date '%s'" % (st, a))
        a_fmt = a_datetime.strftime("%Y-%m-%d")
        if v == '+':
            res_dt = a_datetime + days*oneday
        else:
            res_dt = a_datetime - days*oneday
        res = res_dt.strftime("%Y-%m-%d")
        return("%s %s %s days = %s" % (a_fmt, v, days, res))
    m = calc_date_regex.match(st)
    if m:
        # using date
        a = m.group(1).strip()
        v = m.group(2).strip()
        n = m.group(3).strip()
        # n must be a date
        try:
            a_datetime = datetime.datetime.combine(parse_date(a),time)
        except:
            return(
                "error processing '%s': could not parse date '%s'" % (st, a))
        a_fmt = a_datetime.strftime("%Y-%m-%d")
        if v == '+':
            return(
                "error processing '%s': '+' can only be used with 'days'" %
                    (st))
        else:
            try:
                b_datetime = datetime.datetime.combine(parse_date(n),time)
            except:
                return(
                    "error processing '%s': could not parse date '%s'" %
                        (st, n))
            dt1 = max(a_datetime, b_datetime)
            dt2 = min(a_datetime, b_datetime)
            datedelta = dt1 - dt2
            num_days = datedelta.days
            if num_days == 1:
                d = 'day'
            else:
                d = 'days'
            return("%s - %s = %s %s " % (dt1.strftime("%Y-%m-%d"),
                dt2.strftime("%Y-%m-%d"), num_days, d))
    return("error parsing '%s'. (The '+' or '-' should have a space on each side.)" % st)


def year2string(startyear, endyear):
    """compute difference and append suffix"""
    diff = int(endyear) - int(startyear)
    suffix = 'th'
    if diff < 4 or diff > 20:
        if diff%10 == 1:
            suffix = 'st'
        elif diff%10 == 2:
            suffix = 'nd'
        elif diff%10 == 3:
            suffix = 'rd'
    return "%d%s" % (diff, suffix)

def sort_listofhashes_by_field(listofhashes, *fields):
    lst = []
    thefields = []
    for item in fields:
        thefields.append(item)
    if thefields[0] == 'd':
        thefields.append('B')
    elif thefields[0] == 'B':
        thefields.append('d')
    thefields.append('S')
    for i in range(len(listofhashes)):
        try:
            t = [listofhashes[i][field] for field in thefields]
            lst.append([t, i])
        except:
            print('exception in sort_listofhashes_by_field')
            print('the problem hash:')
            print(listofhashes[i])
            print('thefields:')
            print(thefields)
            print(sys.exc_info())
            raise Exception()
    lst.sort()
    indices = [l[1] for l in lst]
    return [listofhashes[i] for i in indices]

def get_attrs():
    codes = {}
    all_off=os.popen("tput sgr0").read()
    if all_off:
        codes['black'] = os.popen("tput setaf 0").read()
        codes['red'] = os.popen("tput setaf 1").read()
        codes['green']=os.popen("tput setaf 2").read()
        codes['yellow']=os.popen("tput setaf 3").read()
        codes['blue']=os.popen("tput setaf 4").read()
        codes['magenta']=os.popen("tput setaf 5").read()
        codes['cyan']=os.popen("tput setaf 6").read()
        codes['white']=os.popen("tput setaf 7").read()
        codes['gray']=os.popen("tput setaf 8").read()
        codes['dim']=os.popen("tput sshm").read()
        codes['bold']=os.popen("tput bold").read()
        codes['special']=os.popen("tput sitm").read()
        attrs = {
            -2 : codes[mclr],
            -1 : codes[mclr],
             0 : codes[gclr],
             1 : codes[pclr],
             2 : codes[tclr],
             3 : codes[sclr],
             4 : codes[dclr],
             5 : codes[hclr],
             6 : codes[bclr],
             7 : codes[wclr],
             8 : codes[nclr],
             }
    else:
        all_off = ''
        attrs = {}
        for i in range(-2, 9):
            attrs[i] = ''
    return(all_off, attrs)


def get_args(cmd):
    "Show the appropriate help screen and prompt for options."
    # The data version. There are separate curses and wx versions.
    global msg
    cmdlc = cmd.lower()
    get_help(cmdlc) 
    print("\n".join(msg))
    argstr = raw_input('%s: ' % nameHash[cmdlc])
    args = argstr.split(' ')
    return(args)

def get_help(cmd):
    "Return the relevant help screen for cmd."
    global msg
    cmdlc = cmd.lower()
    options = None
    args = ['-h']
    try:
        (options, args) = parserHash[cmdlc].parse_args(args)
    except:
        pass

def clear_msg():
    global msg
    msg = []

def parse_args(cmd, args=[], start=None, stop=None):
    """Parse args using the relevant parser for cmd. Perform consistency
    checks for begin, days and end and for opening and closing."""
    cmdlc = cmd.lower()
    cmduc = cmd.upper()
    options = None
    errors = []
    if cmdlc == 'a':
        startdate = get_today()
        stopdate = get_today() + (int(agenda)-1)*oneday
    else:
        startdate = get_today() - int(recent)*oneday
        stopdate = get_today() + (int(next)-int(recent)-1)*oneday
    
    if start:
        startdate = parse_date(start)
    if stop:
        stopdate = parse_date(stop)

    if cmdlc in ['l', 'b', 'r', 'a']:
        if cmduc == cmd:
            args = get_args(cmdlc) 
        try:
            (def_opts, toss) = parserHash[cmdlc].parse_args([])
            try:
                (options, toss) = parserHash[cmdlc].parse_args(args)
            except:
                errors = ["could not parse %s using parserHash['%s']" % 
                        (args, cmdlc)]
                return({}, errors)

            new_begin = has(options.__dict__, 'begin') and \
                    type(options.__dict__['begin']) == str
            new_end = has(options.__dict__, 'end') and \
                    type(options.__dict__['end']) == str
            options.view = cmdlc

            new_days = False
            try:
                days = int(options.days)
                options.days = int(options.days)
                def_days = int(def_opts.days)
                assert days > 0
            except:
                msg.append('option -d: invalid value (%s) for DAYS' 
                        % options.days)
                raise OptionValueError('DAYS must be a positive integer.')
            # agenda
            if new_begin:
                options.begin = parse_date(options.begin)
            else:
                options.begin = startdate
                
            if new_end:
                options.end = parse_date(options.end)
            else:
                options.end = stopdate

            if days != def_days:
                new_days = True
            if cmdlc == 'a' and new_end and new_days:
                errors.append('At most one of END and DAYS can be set')
                raise OptionValueError(
                        'At most one of END and DAYS can be set')
            elif new_begin and new_end and new_days:
                errors.append('At most two of BEGIN, END and DAYS can be set')
                raise OptionValueError(
                        'At most two of BEGIN, END and DAYS can be set')

            if (new_begin or cmdlc == 'a') and not new_end:
                options.end = options.begin + (days-1)*oneday
            elif new_end and not (new_begin or cmdlc == 'a'):
                options.begin = options.end - (days-1)*oneday
            elif not (new_begin or new_end):
                options.end = options.begin + (days-1)*oneday

            options.begin = parse_date(options.begin)
            options.end = parse_date(options.end)
            if options.begin > options.end:
                errors.append('option conflict: END (%s) occurs before BEGIN (%s)' % (options.end, options.begin))
                raise OptionValueError('end must not be sooner than begin')
            # opening, closing, minimum (for busy/free)
            if parserHash[cmdlc].has_option('-O') and parserHash[cmdlc].has_option('-C'):
                opening = parse(options.opening)
                closing = parse(options.closing)
                earliest_closing = opening + options.minimum*oneminute
                options.opening = format_date(opening, timefmt)
                options.closing = format_date(closing, timefmt)
                if earliest_closing > closing:
                    errors.append('option conflict: CLOSING (%s) must occur at least MINIMUM (%s) minutes after OPENING (%s)' % (options.closing, options.minimum, options.opening)) 
                    raise OptionValueError(
                        'closing must not be later than opening')
            if parserHash[cmdlc].has_option('-S'):
                tmp = int(options.slotsize)
                if 60 % tmp == 0:
                    slotsize = tmp
            if parserHash[cmdlc].has_option('-v') and \
                    options.toggle_verbose:
                options.verbose_toggle = not verbose_toggle
            else:
                options.verbose_toggle = verbose_toggle
            
                
            if parserHash[cmdlc].has_option('-s') and cmdlc != 'b':
                available_opts = ['e', 'a', 't', 'b', 'f', 'w', 'n']
                opts = options.show
                opts_list = list(opts)
                if cmdlc == 'a':
                    if opts != agenda_show:
                        opstr = ''
                        for i in opts_list:
                            if i in available_opts:
                                opstr += i
                        if  opstr and opstr != agenda_show: 
                            options.show = opstr
                        else:
                            options.show = agenda_show                 
                elif cmdlc == 'l':
                    if opts != list_show:
                        opstr = ''
                        for i in opts_list:
                            if i in available_opts:
                                opstr += i
                        if  opstr and opstr != list_show: 
                            options.show = opstr
                        else:
                            options.show = list_show
                elif cmdlc == 'r':
                    if opts != 'ea':
                        opstr = ''
                        for i in list(opts):
                            if i in ['e', 'a']:
                                opstr += i
                        if  opstr and opstr != 'ea': 
                            options.show = opstr
                        else:
                            options.show = 'ea'

        except:
            e = sys.exc_info()
            errors.append(e)

    return(options.__dict__, errors)

