#!/usr/bin/env python

"""Frontend to alarmd for scheduling events and alarms"""

import os
import subprocess
import datetime
import time
from optparse import OptionParser
import alarmed_backend

usage = """alarmed.py -C -L
   or: alarmed.py -C -D [ID]
   or: alarmed.py -C -T [TITLE] -R [ID]
                     [-z DATE] or [-c CRON] or [-n|-t|-i|-l SPEC]
                     -A|[-B|-P|-E CONTENT]"""

parser = OptionParser(usage=usage)
parser.add_option("-C", "--cli", action="store_true", dest="cli",
                  default=False, help="Enable CLI mode")

#list option
parser.add_option("-L", "--list", dest="list",
                  help="List existing events", action="store_true")
#remove option
parser.add_option("-D", "--delete", dest="delete",
                  help="Delete event with specified ID",
                  metavar="'ID'")
#toggle functions
parser.add_option("-e", "--enable", dest="enable",
                  help="Enable event with specified ID",
                  metavar="'ID'")
parser.add_option("-d", "--disable", dest="disable",
                  help="Disable event with specified ID",
                  metavar="'ID'")
#edit option
parser.add_option("-R", "--replace", dest="replace",
                  help="Replace newly created event with specified ID, optional",
                  metavar="'ID'")
#title
parser.add_option("-T", "--title", dest="title",
                  help="Set the title of the event to TITLE",
                  metavar="'TITLE'")
#ways of setting the time
parser.add_option("-z", "--date", dest="datetime",
                  help="Set the date using coreutils date syntax, for example 'now +5 minutes'",
                  metavar="'DATE'")
parser.add_option("-c", "--cron", dest="cron",
                  help="Supply recurrence using cron-like syntax. Make sure to use quotes, for example: '5 * 12,14,16 02,02 3'",
                  metavar="'CRON'")
parser.add_option("-i", "--in", dest="in_",
                  help="Wait PERIOD{s|m|h|d} units until execution. s,m,h,d are seconds, minutes, hours, days. For example 23s or 5h",
                  metavar="'PERIOD'")
parser.add_option("-n", "--next", dest="next",
                  help="Run on upcoming day of week at day:hh:mm. day is one of [mon, tue, wed, thu, fri, sat, sun], for example tue:21:15",
                  metavar="'TIME'")
parser.add_option("-t", "--tomorrow", dest="tomorrow",
                  help="Run tomorrow at hh:mm, for example 17:30",
                  metavar="'TIME'")
parser.add_option("-l", "--end-of-month", dest="end_of_month",
                  help="Recurring on last day of month at hh:mm, for example 18:55",
                  metavar="'TIME'")
#type options
parser.add_option("-A", "--alarm", dest="alarm_type",
                  help="Set a new alarm event", action="store_true")
parser.add_option("-B", "--builtin", dest="builtin_type",
                  help="Schedule a builtin function",
                  metavar="'BUILTIN'")
parser.add_option("-P", "--profile", dest="profile_type",
                  help="Schedule a profile switch",
                  metavar="'PROFILE'")
parser.add_option("-E", "--exec", dest="exec_type",
                  help="Schedule a command execution. Make sure to enclose the command in quotes!",
                  metavar="'COMMAND'")
#preset options
parser.add_option("-S", "--preset", dest="preset_type",
                  help="Schedule a command execution using a preset, specify the preset name using quotes!",
                  metavar="'PRESET'")
parser.add_option("-w", "--list-presets", dest="list_presets",
                  help="List existing presets", action="store_true")
parser.add_option("-x", "--delete-preset", dest="delete_preset",
                  help="Delete preset with specified name",
                  metavar="'PRESET'")
parser.add_option("-y", "--save-preset", dest="save_preset",
                  help="Save a preset. Use -T/--title to specify the title as well!",
                  metavar="'COMMAND'")
(options, args) = parser.parse_args()
#if CLI option is not given, launch GUI
if not options.cli:
    print ":: Starting alarmed GUI"
    import alarmed_gui

o = options
#if list is chosen, just list and exit
if o.list:
    eventInfos = alarmed_backend.loadEventList()
    if len(eventInfos) == 0:
        print "No events specified"
    else:
        for i in eventInfos:
            print "Event ID: " + str(i["id"])
            for info in i:
                if info != "id":
                    if info == "ttime":
                        print ("\t" + info + ": " + str(i[info]))
                        print ("\t" + "datetime: " +
                                 datetime.datetime.fromtimestamp(
                                 i[info]).strftime("%d.%m.%Y, %H:%M"))
                    else:
                        print "\t" + info + ": " + unicode(i[info]).encode("utf-8") 
    exit(0)
#check if GUI is running
try:
    lock = open("/tmp/alarmed.lock", "r")
    lock.close()
    lock = True
except:
    lock = False
if lock:
    print """It seems like Alarmed GUI is running. Close it first if you want to use the CLI interface or remove /tmp/alarmed.lock"""
    exit(1)
#if delete is chosen, delete and exit
if o.delete:
    print alarmed_backend.deleteEvent(int(o.delete))
    exit(0)
#if user is toggling, do that and exit
if o.enable:
    eventInfo = alarmed_backend.loadEventInfo(o.enable)
    if eventInfo:
        a = alarmed_backend.makeEvent(eventInfo, int(eventInfo["id"]))
        if a:
            print "Enabled event with ID " + str(o.enable)
        else:
            print "Error occured enabling event, please report a bug"
    else:
        print "Event with ID " + str(o.enable) + " does not exist"
    exit(0)
if o.disable:
    eventInfo = alarmed_backend.loadEventInfo(o.disable)
    if eventInfo:
        alarmed_backend.deleteEventAlarmd(eventInfo)
        print "Disabled event with ID " + str(o.disable)
    else:
        print "Event with ID " + str(o.disable) + " does not exist"
    exit(0)
#if list-presets is chosen, list them and exit
if o.list_presets:
    presetList = alarmed_backend.loadPresetList()
    if len(presetList) == 0:
        print "No presets specified"
    for preset in presetList:
        print "Preset name: " + preset[0]
        print "\t" + preset[1]
    exit(0)
#if delete-preset is chosen, delete that and exit
if o.delete_preset:
    preset = []
    preset.append(o.delete_preset)
    print alarmed_backend.deletePreset(preset)
    exit(0)
#if save-preset is chosen, check if title is there an save preset
if o.save_preset:
    if o.title == None:
        parser.error("When saving a preset, please also specify a title!")
        exit(1)
    preset = []
    preset.append(o.title)
    preset.append(o.save_preset)
    alarmed_backend.savePreset(preset)
    print 'Created new preset "' + o.title + '"'
    exit(0)
#check if combination of options is valid
def n_are_true(list, n):
    counter = 0
    for e in list:
        if e: counter += 1
    if counter == n: return True
    else: return False
if not (o.title or o.preset_type):
    parser.error("You have to supply a title")
got_type = n_are_true([o.alarm_type, o.builtin_type, o.profile_type,
                       o.exec_type, o.preset_type],1)
if not got_type:
    parser.error("You have to specify exactly one of {A|B|P|E|S}")
got_time = n_are_true([o.end_of_month, o.in_, o.tomorrow, o.next, 
                       o.datetime, o.cron],1)
if not got_time:
    parser.error("You have to specify exactly one of {z|c|n|t|i|l}")
#prepare for parsing
eventInfo = {}
weekdays = []
lists = alarmed_backend.loadFieldsDict()
#if replace is set, set replace ;-)
if o.replace:
    old_id = o.replace
else:
    old_id = False
#parse cron-like input
if o.cron:
    cron = alarmed_backend.interpretCronString(o.cron)
    if not cron[0]:
        parser.error(cron[1])
    else:
        eventInfo["cron"] = cron[1]
        eventInfo["schedule"] = lists["schedules"]["cron"]
        t = datetime.datetime.today()
        t = t + datetime.timedelta(seconds=+10)
        eventInfo["ttime"] = int(time.mktime(t.timetuple()))
        
#parse "next tuesday" input
if o.next:
    s = o.next.split(":")
    if len(s) != 3:
        parser.error("Next SPEC must be in format ddd:hh:mm; Got: " +
         str(s) + "instead")
    if s[0] not in ['mon','tue','wed','thu','fri','sat','sun',]:
        parser.error("Got invalid weekday: " + s[0])
    if not (0 <= int(s[1]) <= 23):
        parser.error("Got invalid hour: " + s[1])
    if not (0 <= int(s[2]) <= 59):
        parser.error("Got invalid minute: " + s[2])
    if s[0] == "mon": day = 0
    if s[0] == "tue": day = 1
    if s[0] == "wed": day = 2
    if s[0] == "thu": day = 3
    if s[0] == "fri": day = 4
    if s[0] == "sat": day = 5
    if s[0] == "sun": day = 6
    if not day:
        parser.error("Day " + s[0] + "seems to be invalid")
    t = datetime.datetime.today()
    t = t + datetime.timedelta(days=+1)
    while t.weekday() != day:
        print t.weekday()
        print day
        t = t + datetime.timedelta(days=+1)
    t = t.replace(hour=int(s[1]),minute=int(s[2]))
    eventInfo["ttime"] = int(time.mktime(t.timetuple()))
    eventInfo["schedule"] = lists["schedules"]["next"]
if o.end_of_month:
    s = o.end_of_month.split(":")
    if len(s) != 2:
        parser.error(
           "Last day of month SPEC must be in format ddd:hh:mm; Got: " +
           str(s) + "instead")
    if not (0 <= int(s[0]) <= 23):
        parser.error("Got invalid hour: " + s[0])
    if not (0 <= int(s[1]) <= 59):
        parser.error("Got invalid minute: " + s[1])
    t = datetime.datetime.today()
    t = t + datetime.timedelta(days=+1)
    t = t.replace(hour=s[1],minute=s[2])
    eventInfo["ttime"] = int(time.mktime(t.timetuple()))
    eventInfo["schedule"] = lists["schedules"]["end_of_month"]
if o.tomorrow:
    s = o.tomorrow.split(":")
    if len(s) != 2:
        parser.error(
                  "Tomorrow 'TIME' must be in format ddd:hh:mm; Got: " +
                  str(s) + "instead")
    if not (0 <= int(s[0]) <= 23):
        parser.error("Got invalid hour: " + s[0])
    if not (0 <= int(s[1]) <= 59):
        parser.error("Got invalid minute: " + s[1])
    t = datetime.datetime.today()
    t = t + datetime.timedelta(days=+1)
    t = t.replace(hour=int(s[0]),minute=int(s[1]))
    eventInfo["ttime"] = int(time.mktime(t.timetuple()))
    eventInfo["schedule"] = lists["schedules"]["tomorrow"]
#save "in x units" input
if o.in_:
    try:
        t = int(o.in_[:len(o.in_)-1])
        q = o.in_[len(o.in_)-1:]
    except ValueError:
        parser.error("Your relative time - " + str(o.in_) +
                     " is invalid")
    if (type(t) == type(1)) and (type(q) == type("a")):
        i = lists["intervals"]
        if q == "s":
            secs = t
            eventInfo["interval"] = i["seconds"]
        elif q == "m":
            secs = t*60
            eventInfo["interval"] = i["minutes"]
        elif q == "h":
            secs = t*60*60
            eventInfo["interval"] = i["hours"]
        elif q == "d":
            secs = t*60*60*24
            eventInfo["interval"] = i["days"]
        else:
            parser.error(q + " is not a valid time quantifier")
        eventInfo["number"] = str(t)
        t = datetime.datetime.today()
        t = t + datetime.timedelta(seconds=+secs)
        eventInfo["ttime"] = int(time.mktime(t.timetuple()))
        eventInfo["schedule"] = lists["schedules"]["in"]
    else:
        parser.error("Your relative time - " + str(o.in_) +
                     " is invalid")
#save the ttime
if o.datetime:
    ttime = alarmed_backend.interpretDateString(o.datetime)
    if not ttime[0]:
        parser.error(ttime[1])
    else:
        eventInfo["ttime"] = ttime[1]
        eventInfo["schedule"] = lists["schedules"]["date"]
        eventInfo["date"] = o.datetime
#save the type and set the content
t = lists["types"]
if o.alarm_type:
    eventInfo["type"] = t["alarm_type"]
elif o.builtin_type:
    b = lists["builtins"]
    try: eventInfo["content"] = b[o.builtin_type]
    except KeyError:
        parser.error(o.builtin_type +
                     " is not an existing builtin function\n" + 
                     "Valid functions are: " + str(lists["builtins"]))
    eventInfo["type"] = t["builtin_type"]
elif o.profile_type:
    p = alarmed_backend.getAvailableProfiles()
    if o.profile_type not in p:
        parser.error("Profile " + o.profile_type + " does not exist\n" +
                     "Valid profiles are: " + str(p))
    eventInfo["content"] = o.profile_type
    eventInfo["type"] = t["profile_type"]
elif o.exec_type:
    eventInfo["type"] = t["exec_type"]
    eventInfo["content"] = o.exec_type
elif o.preset_type:
    preset = alarmed_backend.loadPreset(o.preset_type)
    eventInfo["type"] = t["exec_type"]
    eventInfo["content"] = preset[1]
    eventInfo["title"] = preset[0]
#save title
if o.title:
    eventInfo["title"] = o.title
a = alarmed_backend.makeEvent(eventInfo,old_id)
if a == "invalid":
    print "Please pick a date & time in the future"
    exit(1)
else:
    print "Event added successfully. ID: " + str(a)
