# rc.py
#
# Copyright (c) 2009 Daniel Graham <daniel.graham@duke.edu>. All rights
# reserved.
# License: This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or (at your
# option) any later version. [http://www.gnu.org/licenses/gpl.html]
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
import sys, os, os.path, codecs
has_etmrc = False

# TODO: history lists -> hashes with value equal to number of uses. Show defaults plus 20 most frequently used. Store in ~/.etm.

rc = []
added = []
msg = []
error = []
warn = []
fatal = False
warning = False
set_ed = False
search_path = os.getenv('PATH').split(os.pathsep)
cwd = os.getcwd()
homedir = os.path.expanduser("~")
etmrc_maybe = os.path.join(cwd, 'etmrc')
if os.path.isfile(etmrc_maybe):
    etmdir = cwd
    etmrc = etmrc_maybe
else:
    etmdir = os.path.join(homedir, ".etm")
    etmrc = os.path.join(homedir, ".etm", "etmrc")

if sys.platform == 'darwin':
    mac = True
    d_bgcolor = 'GRAY70'
    d_basefont = 12
    d_htmlfont = 8
    d_htmlprintfont = 6
    d_width = 560 
    d_height = 360
else:
    mac = False
    d_bgcolor = 'GRAY85'
    d_basefont = 8
    d_htmlfont = 4
    d_htmlprintfont = 6
    d_width = 640
    d_height = 360

def PathSearch(filename):
    for path in search_path:
        candidate = os.path.join(path,filename)
        if os.path.os.path.isfile(candidate):
            return os.path.abspath(candidate)
    return ''
    
def check(variable):
    global rc, added
    name = variable[0]
    defining_code = variable[1]
    description = "\n### ".join(variable[2:])
    if description:
        rc.append("\n### %s" % description)
    # if globals().has_key(name):
    if name in globals():
        if type(globals()[name]) == str:
            if globals()[name] != "":
                s = "%s = '''%s'''" % (name, globals()[name])
                # print "not empty: %s = %s" % (name, globals()[name])
            else:
                s = "%s = ''" % (name)
        else:
            s = "%s = %s" % (name, globals()[name])
        rc.append(s)
    else:
        res = ''
        if type(defining_code) == str:
            try:
                exec "res = %s" % defining_code
            except:
                exec "res = '%s'" % defining_code
        elif type(defining_code) in [tuple, list]:
            exec "res = (%s)" % repr(defining_code)
        else:
            exec "res = %s" % defining_code
        globals()[name] = res
        if globals()[name] != "not set":
            # print "Adding: %s" % name
            if type(res) == str:
                if res != "":
                    s = "%s = '''%s'''" % (name, res)
                else:
                    s = "%s = ''" % (name)
            elif type(res) in [tuple, list]:
                s = "%s = %s" % (name, repr(res))
            else:
                s = "%s = %s" % (name, res)
            added.append(s)
            rc.append(s)


def set_etmrc():
    global has_etmrc
    if os.path.isfile(etmrc):
        has_etmrc = True
    return etmrc

def check_etmdir():
    if not os.path.isdir(etmdir):
        os.mkdir(etmdir)
        print("""
Created '%s' to hold etm system files.
\n""" % (etmdir))
    return os.path.isdir(etmdata)

def check_etmdata():
    if not os.path.isdir(etmdata):
        os.mkdir(etmdata)
        print("""
Created '%s' to hold project files.
Users of earlier versions of etm will need either to move their
project files to this subdirectory or to correct the entry for
etmdata in '%s'.\n""" % (etmdata, etmrc))
    return os.path.isdir(etmdata)

def check_etmical():
    if not os.path.isdir(etmical):
        os.mkdir(etmical)
        print("""
Created '%s' to hold exported ical calendar files.
This location is specified by  the entry for etmical in '%s'.\n""" % (etmical,etmrc))
    return os.path.isdir(etmdata)

def set_editor_settings():
    global rc, added, set_ed
    chooselater = ['choose later', '', '', '']
    comment = ""
    selected = []
    unselected = []
    if set_ed:
        return None
    else:
        set_ed = True
    if mac:
        options = {
            'bbedit' : [
                "editold = '''%(e)s +%(n)s -w --new-window %(f)s'''",
                "editnew = '''%(e)s +99999 -w --new-window %(f)s'''"
            ],
            'edit' : [
                "editold = '''%(e)s +%(n)s -w --new-window %(f)s'''",
                "editnew = '''%(e)s +99999 -w --new-window %(f)s'''"
            ],
            'mate' : [
                "editold = '''%(e)s -l %(n)s -w %(f)s'''",
                "editnew = '''%(e)s -l 99999 -w %(f)s'''"
            ],
            'vim' : [
                "editold = '''%(e)s -g -p -f +%(n)s %(f)s'''",
                "editnew = '''%(e)s -g -p -f + %(f)s'''"
            ],
        }
        for name in ['bbedit','mate', 'vim', 'edit']:
            editor = PathSearch(name)
            # select the first that works and comment out the others
            if editor:
                if comment != "# ":
                    selected = (editor,
                        options[name][0], options[name][1])
                else:
                    unselected.append(name)
                rc.append("%seditor = '''%s'''" % (comment, editor))
                added.append("%seditor = '''%s'''" % (comment, editor))
                rc.append("%s%s" % (comment, options[name][0]))
                added.append("%s%s" % (comment, options[name][0]))
                rc.append("%s%s" % (comment, options[name][1]))
                added.append("%s%s" % (comment, options[name][1]))
                comment = "# "
    else: # not mac
        options = {
            'gvim' : [
                "editold = '''%(e)s -f +%(n)s %(f)s'''",
                "editnew = '''%(e)s -f + %(f)s'''"
            ],
            'emacs' : [
                "editold = '''%(e)s +%(n)s %(f)s'''",
                "editnew = '''%(e)s +999999 %(f)s'''"
            ]
        }
        for name in ['gvim','emacs']:
            editor = PathSearch(name)
            # select the first that works and comment out the others
            if editor:
                if comment != "# ":
                    selected = (editor,
                        options[name][0], options[name][1])
                else:
                    unselected.append(name)
                rc.append("%seditor = '''%s'''" % (comment, editor))
                added.append("%seditor = '''%s'''" % (comment, editor))
                rc.append("%s%s" % (comment, options[name][0]))
                added.append("%s%s" % (comment, options[name][0]))
                rc.append("%s%s" % (comment, options[name][1]))
                added.append("%s%s" % (comment, options[name][1]))
                comment = "# "
    if selected == []:
        rc.append("editor = ''")
        rc.append("editold = ''")
        rc.append("editnew = ''")
        print("""
        You will need to specify values for:

            editor
            editold
            editnew

        in %s.\n""" % etmrc)
    else:
        print("""
Project files will be located in the directory

    etmdata = '%s'

The following settings were made for your external editor:

    editor = '%s'
    editold = '%s'
    editnew  = '%s'

Edit %s if you wish to make changes.\n""" % (etmdata,
        selected[0], selected[1], selected[2],
        etmrc))
        if len(unselected) > 0:
            print("""\
Comparable settings were also made for

    %s

but were commented out. \n""" % ", ".join(unselected))
    return None

def set_editor():
    if not set_ed:
        set_editor_settings()
    return "not set"

def set_editold():
    if not set_ed:
        set_editor_settings()
    return "not set"

def set_editnew():
    if not set_ed:
        set_editor_settings()
    return "not set"

def make_etmrc():
    fo = open("%s" % etmrc,'w')
    fo.write("""\
### Configuration settings for etm (Event and Task Manager)
###
### etm's current default settings will be written to '~/.etm/etmrc' if
### this file doesn't already exist. This means you can always restore
### the default settings by either removing or renaming ~/.etm/etmrc.
### Further, if you would like to restore some but not all of the default
### settings, simply erase the settings you want restored and the next
### time e.py is run, your ~/.etm/etmrc will be recreated
### with your settings together with the default settings for the ones
### you erased.
###\n""")
    for line in rc:
        fo.write("%s\n" % line)
    fo.close()

variables = [
    ['encoding', '"UTF-8"', 
        'The default encoding for terminal output. Your terminal must',
        ' be able to support output using this encoding.'],
    ['etmdata', "%s" % os.path.join(etmdir,"data"),
        'The directory in which project files will be stored.'],
    ['etmical', "%s" % os.path.join(etmdir, "ical"),
        'The directory in which exported iCal files will be stored.'],
    ['inc', 60,
        'The interval in seconds between alert updates'],
    ['task', '"tasks"',
        'The root for task data files. By default new tasks will be saved to',
        '<current month number>-<task>.txt.'],
    ['done', '"done"',
        'The root for finished copies of repeating tasks. By default new events',
        'will be saved to <current month number>-<done>.txt.'],
    ['event', '"evnts"',
        'The root for event data files. By default new events will be saved to',
        '<current month number>-<event>.txt.'],
    ['action', '"actn"',
        'The root for action data files. By default new action entries will be saved to',
        '<current month number>-<action>.txt.'],
    ['note', '"note"',
        'The root for note data files. By default new note entries will be saved to',
        '<current month number>-<note>.txt.'],
    ['numbaks', 3,
        'The number of backups of data files to keep'],
    ['context', None,
        'The default context for new events and tasks'],
    ['keyword', None,
        'The default keyword for new events and tasks'],
    ['editor', '"%s" % set_editor()',
        'Edit settings',
        'editor:  the full path to the external editor',
        'editold: the command for editing an old task, event or action',
        'editnew: the command for editing an new task, event or action',
        'Both editold and editnew use the following SUBSTITUTIONS:',
        '   %(e)s -> editor',
        '   %(n)s -> the line number to edit',
        '   %(f)s -> the file name'     ],
    ['editold', '"%s" % set_editold()', ''],
    ['editnew', '"%s" % set_editnew()',''],
    ['alertcmd', '''''',
        'The command to run when alerts are triggered with these replacements:',
        '   %(t)s -> thetimeis <current time>',
        '   %(T)s -> <current time>',
        '   %(m)s -> the text of the alert message',
        'If left blank, the default, then an internal alert will be displayed.'],
    ['seconds', 10, 
            'The number of seconds to display the internal alert. If set equal',
            'to 0, then the alert will be displayed until dismissed by the user.'],
    ['xpos', 20, 
            'The x coordinate for the top left corner of the wx window'],
    ['ypos', 40, 
            'The y coordinate for the top left corner of the wx window'],
    ['wxwidth', d_width, 
            'The starting and minimum width for the wx window'],
    ['wxheight', d_height, 
            'The starting and minimum height for the wx window'],
    ['confirm_cancel_entry', False,
        'Request confirmation when canceling an entry in the entry bar'],
    ['confirm_delete', True,
        'Request confirmation when deleting an item'],
    ['confirm_modify', True,
        'Request confirmation when creating or modifying an item'],
    ['confirm_finish', True,
        'Request confirmation when marking a task finished or marking',
        'a task unfinished'],
    ['delay', 2, 
            'The number of seconds to display temporary messages',
            'in the etm wx gui'],
    ['recent', 0,
        'The default number of days before today to begin the display in list',
        'busy/free and reckoning views'],
    ['next', 7,
        'The default number of days to display in list, busy/free and',
        'reckoning views'],
    ['agenda', 3,
        'The default number of days to display in the agenda view'],
    ['verbose', True,
        'Start in verbose mode showing alerts, keywords and notes (True)',
        'or hide alerts, keywords and notes (False).'],
    ['extent', 30,
        'New events with a starttime but no endtime will, by default,',
        'have an endtime equal to this number of minutes after starttime',
        'when displaying busy and free times'],
    ['increment', 1,
        'When aggregating times for events and action entries, if',
        'the actual number of minutes is m and the increment is i, then',
        'the number of minutes added will be ceil(m/i)*i. E.g. if m=20',
        'and i=15, then ceil(20.0/15.0)*15 = 30 minutes will be added.'],
    ['hours_minutes', False,
        'Display time spent in time reports using hours and minutes (True)',
        'or hours rounded up to the nearest tenth (False).'],
    ['c_position', 0,
        'Time report default setting. If c_position is positive, subtotal by',
        'category in an order determined by the value of c_position.'],
    ['d_position', 0,
        'Time report default setting. If d_position is positive, subtotal by',
        'date in an order determined by the value of d_position.'],
    ['k_level', 0,
        'Time report default setting. If k_level is positive, subtotal by',
        'keywords to a depth corresponding to the value of k_level. 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.'],
    ['use_ampm', True,
        'Display time using 12 hour, AM/PM format (True) or 24 hour format (False)'],
    ['opening', "8:00a",
        'The opening or earliest time to consider, by default, in displaying ',
        'unscheduled periods'],
    ['closing', "5:00p",
        'The closing or latest time to consider, by default, in displaying ',
        'unscheduled periods'],
    ['include', "bf",
        "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) to include, by default, in the",
        "busy/free view."],
    ['minimum', 30,
        'The minimum number of minutes for an free (unscheduled) period to',
        'be displayed'],
    ['slack', 15,
        'The number of minutes before and after busy periods to omit',
        'in computing free periods'],
    ['busychar', '*',
        'The character used to indicate busy periods'],
    ['freechar', '-',
        'The character used to indicate free periods'],
    ['conflictchar', '#',
        'The character used to indicate a more than one event',
        'scheduled for the same time period'],
    ['slotsize', 10,
        'The number of minutes expressed by one character in busy/free time',
        'bars. It must be true that 60 % slotsize = 0. Recommended choices', 
        'are 10, 12 or 15. Using the default, 10, the time bar for 8am - 8pm',
        'would be 12*(60/10) = 72 characters wide. Using 12, the same 12',
        'hour time bar would be 12*(60/12) = 60 characters wide.'],
    ['agenda_show', 'etb', 
            "Show 'e' (events), 'a' (actions), 't' (available tasks)",
            "'b' (tasks on their begin by as well as their due dates),"
            "'f' (finished tasks) and 'w' (waiting tasks) by default in",
            "agenda view"],
    ['agenda_history', [], 
        'A list of option strings to include in the history list for',
        'agenda view.'],
    ['list_show', 'eatnbfw', 
            "Show 'e' (events), 'a' (actions), 't' (available tasks)",
            "'b' (tasks on their begin by as well as their due dates),"
            "'f' (finished tasks) and 'w' (waiting tasks) by default in",
            "list view"],
    ['list_history', [], 
        'A list of option strings to include in the history list for',
        'list view.'],
    ['busy_history', [], 
        'A list of option strings to include in the history list for',
        'busy/free view.'],
    ['reckoning_history', [], 
        'A list of option strings to include in the history list for',
        'reckoning view.'],
    ['templates', [],
        'A list of templates for new tasks and events'],
    ['weekday_fmt', '%a, %b %d',
        'The format to use for the date when grouping by date',
        'using the directives listed above.'],
    ['week_begin', 6,
        'The first day of the week. Monday = 0, Sunday = 6.'],
    ['location', [],
        'The USNO location for sun/moon data. Either a US city-state',
        '2-tuple such as [\'Chapel Hill\', \'NC\'] or',
        'a placename-longitude-latitude 7-tuple such as ',
        '[\'Home\', \'W\', 79, 0, \'N\', 35, 54]',
    ],
    ['basefontsize', d_basefont, 
        'The base font size'],
    ['htmlfont', d_htmlfont, 
        'The starting html font size'],
    ['htmlprintfont', d_htmlprintfont, 
        'The starting html printing font size'],
    ['detail_bgcolor', d_bgcolor, 
            'The background color for the detail (information) bar',
            'in the etm wx gui'],
    ['detail_fgcolor', 'Black', 
            'The foreground color for the detail (information) bar',
            'in the etm wx gui'],
    ['entry_fgcolor', 'Black', 
            'The foreground color for the entry bar',
            'in the etm wx gui'],
    ['entry_bgcolor', 'White', 
            'The background color for the entry bar',
            'in the etm wx gui'],
    ['main_bgcolor', 'White', 
            'The background color for the main window and the entry bar',
            'in the etm wx gui'],
    ['main_fgcolor', 'Black', 
            'The default foreground color for the main window',
            'in the etm wx gui'],
    ['main_dtls', '#555555', 
            'The foreground color for the main window details rows',
            'in the etm wx gui'],
    ['main_hclr', 'Blue',
        'The color to use for the header title',
            'in the etm wx gui'],
    ['main_gclr', 'Green',
        'The color to use for groupby headings',
            'in the etm wx gui'],
    ['main_pclr', 'Red',
        'The color to use for pastdue tasks',
            'in the etm wx gui'],
    ['main_tclr', 'Magenta',
        'The color to use for tasks due today',
            'in the etm wx gui'],
    ['main_sclr', 'DarkMagenta',
        'The color to use for tasks due within agenda days',
            'in the etm wx gui'],
    ['main_dclr', 'MediumBlue',
        'The color to use for tasks due within NEXT days',
            'in the etm wx gui'],
    ['main_mclr', 'Black',
        'The color to use for tasks due in more than NEXT days',
            'in the etm wx gui' ],
    ['main_bclr', '#669933',
        'The color to use for tasks with with begin but not due',
            'dates within the displayed interval in the etm wx gui' ],
    ['main_wclr', '#6699CC',
        'The color to use for tasks with unfinished prerequisites',
            'in the etm wx gui' ],
    ['main_nclr', '#555555',
        'The color to use for notes in the etm wx gui'],
    ['print_fgcolor', 'Black', 
            'The default printing foreground color'],
    ['print_dtls', 'Black', 
            'The printing foreground color for details rows'],
    ['print_hclr', 'Black',
        'The printing color for the header title'],
    ['print_gclr', 'Black',
        'The printing color to use for groupby headings'],
    ['print_pclr', 'Black',
        'The printing color to use for pastdue tasks'],
    ['print_tclr', 'Black',
        'The printing color to use for tasks due today'],
    ['print_sclr', 'Black',
        'The printing color to use for tasks due within agenda days'],
    ['print_dclr', 'Black',
        'The printing color to use for tasks due within NEXT days'],
    ['print_mclr', 'Black',
        'The printing color to use for tasks due in more than NEXT days' ],
    ['print_bclr', 'Black',
        'The printing color to use for tasks with with begin but not due',
            'dates within the displayed interval' ],
    ['print_wclr', 'Black',
        'The printing color to use for tasks with unfinished prerequisites'],
    ['print_nclr', 'Black',
        'The printing color to use for notes'],
    ['hclr', 'blue',
        'Possible colors to use here and in other text displays include:',
        '\'black\', \'red\', \'green\', \'yellow\', \'blue\', \'magenta\',',
        '\'cyan\' and \'white\'. Be sure not to capitalize these color names.',
        'The color to use for the header title in the text display'],
    ['gclr', 'green',
        'The color to use for groupby headings in the text display'],
    ['pclr', 'red',
        'The color to use for pastdue tasks in the text display'],
    ['tclr', 'magenta',
        'The color to use for tasks due today in the text display'],
    ['sclr', 'blue',
        'The color to use for tasks due within agenda days',
        'in the text display'],
    ['dclr', 'blue',
        'The color to use for tasks due within NEXT days',
        'in the text display'],
    ['mclr', 'black',
        'The color to use for tasks due in more than NEXT days',
        'in the text display' ],
    ['bclr', 'cyan',
        'The color to use for tasks with with begin but not due',
            'dates within the displayed interval in the text display' ],
    ['wclr', 'cyan',
        'The color to use for tasks with unfinished prerequisites',
        'in the text display'],
    ['nclr', 'cyan',
        'The color to use for notes in the text display'],
    ['cal_pastcolor', '#FFCCCC',
        'The background color to use for past years in the',
        '12 month calendar'],
    ['cal_currentcolor', '#FFFFCC',
        'The background color to use for the current year in the',
        '12 month calendar'],
    ['cal_futurecolor', '#99CCFF',
        'The background color to use for future years in the',
        '12 month calendar'],
    ['Agenda', '"agenda"',
        'Language customizations:',
        'The word to use in the agenda view title bar.',],
    ['List', '"list"',
        'The word to use in the list view title bar.'],
    ['Busy', '"busy/free"',
        'The word to use in the list view title bar.'],
    ['Reckoning', '"reckoning"',
        'The word to use in the time reckoning title bar.'],
    ['Options', '"options"',
        'The word to use in the prompt for options.'],
    ['NoDueDate', '"Without a due date"',
        'The group title for items without a due date.'],
    ['Quit', '"Quit"',
        'The word to use for quit in the command line.'],
    ['Help', '"Help"',
        'The word to use for help in the command line.'],
    ['minutes', 'minutes from now',
        'In alerts, the string to append for more than one minute'],
    ['minute', 'minute from now',
        'In alerts, the string for exactly one minute'],
    ['rightnow', 'now',
        'In alerts, the string to use for this instant'],
    ['thetimeis', '"The time is"',
        'The prefix for the spoken time'],
    ['weatherlocation', 'USNC0105&u=f',
    'The yahoo weather location code and temperature scale for your area.',
    'Go to http://weather.yahoo.com/, enter your location and hit return.',
    'When the weather page for your location opens, choose view source ',
    '(under the View menu), search for \'forecastrss\' and copy the ',
    'location code that follows \'p=\', e.g., for',
    '       ...forecastrss?p=USNC0120&u=f                                ',
    'the location code would be \'USNC0120&u=f\'. Note: the \'&u=f\'',
    'gives fahrenheit readings while \'&u=c\' would give celcius/centigrade.'],
    ['rising', 'rising', 'Phrase for the barometric pressure is increasing'],
    ['constant', 'steady', 'Phrase for the barometric pressure is constant'],
    ['falling', 'falling', 'Phrase for the barometric pressure is decreasing'],
    ['chill', 'feels like', 'Phrase for the wind chill temperature'],
    ['weather', 'Weather for', 'Phase for weather header'],
    ['current_conditions', 'Current conditions', 'Phrase for current conditions'],
    ['forecast', 'Forecast', 'Phrase for forecast'],
    ]

# main
etmrc = set_etmrc()
if has_etmrc:
    fo = open(etmrc, 'r')
    exec(fo)
    fo.close()

for variable in variables:
    check(variable)

try:
    if sys.stdout.encoding.lower() != encoding.lower():
        # try to reset stdout to the encoding specified in etmrc
        try:
            old_stdout = sys.stdout
            old_encoding = sys.stdout.encoding
            new_stdout = codecs.lookup(encoding)[-1](sys.stdout)
            new_encoding = new_stdout.encoding
            print("""\
    Warning. The encoding of sys.stdout, '%s', does not match the setting
    for 'encoding' in ~/.etm/etmrc, '%s'.""" % 
            (old_encoding, new_encoding))
        except:
            print("""\
    Error. Output using encoding '%s' is not supported by your terminal.
    Please correct the setting for 'encoding' in ~/.etm/etmrc.
    Using encoding '%s' instead.
    """ % (encoding, sys.stdout.encoding))
            # sys.stdout = old_stdout
except:
    pass
    # print('Could not compare encoding with sys.stdout.encoding')



check_etmdir()
check_etmdata()
check_etmical()

if fatal:
    sys.exit()

if len(added) > 0:
    if has_etmrc:
        import shutil
        shutil.copyfile(etmrc, "%s.bak" % etmrc)
        make_etmrc()
        fo = open(etmrc, 'r')
        exec(fo)
        fo.close()
        print """\
IMPORTANT: Your configuration file

    %s

has as been copied to

    %s.bak

and a new configuration file which incorporates your settings together
with

    %s

has been saved as

    %s \n""" % (etmrc, etmrc, "\n    ".join(added), etmrc)

    else:
        make_etmrc()
        print("""\
A new configuration file with default settings has been saved as

    %s \n""" % etmrc)

    print """\
Please remember to vote for etm at <http://freshmeat.net/projects/etm/>
and to send your comments to <daniel.graham@duke.edu>. Continuing
improvement depends upon your feedback.

Thanks for using etm!
"""
    raw_input('Press enter to continue')
    if warning:
        sys.exit()