#!/usr/bin/env python
#
# tms   - A simple (T)ime (M)anagement (S)ystem.  Keep track of time
#         (so I know what I've done) and attempt to do some estimation
#         accuracy tracking as well.  An attempt is far better than
#         doing nothing, or better put, if you aim at nothing, you'll
#         hit it every time.
#
# Copyright (C) 2004, 2007, 2008.  All Rights Reserved.  Michael Davies <michael@the-davies.net>
#
# 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 2 of the
# License, or (at your option) any later version.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
import os
import os.path
import fnmatch
import sys
import datetime
import time
import calendar

__author__ = "Michael Davies (michael@the-davies.net)"
__version__ = "0.1.14" # 1st digit = major, non-compat change.  2nd = minor, func.  3rd = bugfix
__date__ = "Date: 2008/02/18 09:54:19"
__copyright__ = "Copyright (c) 2004, 2007, 2008 Michael Davies"
__license__ = "GPLv2"

#
# User Modifiable Data
#


#
# Classes
#
class TMS(object):

    #
    # Non-User Modifiable Data
    #
    __tms_dir = os.environ['HOME'] + '/.tms/'
    __tms_config_file = __tms_dir + 'config'

    def print_usage(self):
        global my_name
        print my_name + " version:" + __version__ + """
Copyright (C) 2008 Michael Davies <michael@the-davies.net>
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions. See the file COPYING for details.

Syntax: tms [params] <command> [<file(s)>|<task(s)>|<date>]

Commands:
  add <task> - Add a project/task to the list of valid
                  projects/tasks
  del <task> - Remove a task from the list of valid
                  projects/tasks (TBD)
  list - List the valid tasks
  log <date> - Display what you've been working on
  task <task> - Switch to a valid task
  stop - Finish recording for today
  daily - Display a sum of a particular day's tasks (TBD)
  today - Display a sum of today's tasks
  weekly <date> - Display a sum of that week's tasks (partial)
  estimate <task> <total> <est-to-complete> - Add
                or update an estimate for a task (TBD)
  check-estimate <task> - Display the original, revised estimates
                for a task; and display est-to-complete (TBD)
  estimate-stats - Display estimation stats (TBD)
  help | --help | -h | usage - Display this usage information
"""

    def print_version(self):
        global my_name
        print my_name + " Version:" + __version__ + " " + __date__ + "\n"

    def print_today_report(self):
        current_time = datetime.datetime.now()
        dirpath = self.__tms_dir + current_time.strftime("%Y/%m/%d/")
        fd = open(dirpath + "log.txt", 'r')
        all_lines = fd.readlines()
        fd.close()
        mod_all_lines = []
        for line in all_lines:
            mod_all_lines.append(line[:-1]) # chomp
        daily_times = self._process_log_file(mod_all_lines)
        for k, v in daily_times.iteritems():
            print k + "\t\t\t\t" + "%.2f" % daily_times[k]

    def print_current_task(self):
        current_time = datetime.datetime.now()
        dirpath = self.__tms_dir + current_time.strftime("%Y/%m/%d/")
        fd = open(dirpath + "log.txt", 'r')
        all_lines = fd.readlines()
        fd.close()
        # Print the 2nd component of the last line, removing the
        # leading space and the \n off the end
        print all_lines[-1].split(',')[1][1:-1]

    def print_weekly_report(self):
        current_time = datetime.datetime.now()
        week_array = self._find_week(current_time.strftime("%d/%m/%Y"))
        whole_week = {}
        # Now that we have an array of datetimes, see if we have files for them
        err = False
        # Now loop over every day
        for dt in week_array:
            datepath = dt.strftime("%Y/%m/%d/")
            dirpath = self.__tms_dir + datepath
            try: # Emulate a try/catch/finally bock in python < 2.5
                try:
                    fd = open(dirpath + "log.txt", 'r')
                    all_lines = fd.readlines()
                except:
                    err = True
            finally:
                if fd != None:
                    fd.close()
            if not err:
                mod_all_lines = []
                for line in all_lines:
                    mod_all_lines.append(line[:-1]) # chomp
                daily_times = self._process_log_file(mod_all_lines)
                daily_total = 0
                for k, v in daily_times.iteritems():
                    if k == 'STOP':
                        continue
                    daily_total += v
                    if not whole_week.has_key(k):
                        whole_week[k] = {}
                    mytask = whole_week[k]
                    try:
                        mytask[dt.weekday()] += v
                    except:
                        mytask[dt.weekday()] = v
                if not whole_week.has_key('ZZZTOTAL'):
                    whole_week['ZZZTOTAL'] = {}
                whole_week['ZZZTOTAL'][dt.weekday()] = daily_total
        # Now loop over every task
        print "\nSUMMARY OF TASKS FOR WEEK:" + "\n"

        total = 0.0;

        keys = whole_week.keys()
        keys.sort()
        for task in keys:
            if task == 'ZZZTOTAL':
                total = whole_week[task]
                print "============================================================================================="
                print "%35s" % 'TOTAL',
            else:
                print "%35s" % task,
            total = 0
            for day in range (0, 5):
                t = self._print_task_time(whole_week[task], day)
                print "\t" + t,
                total += float(t)
            print "\t|\t" + str(total)


    def _print_task_time(self, dict, index):
        if dict.has_key(index):
            return "%.2f" % dict[index]
        else:
            return "0.00"

    def _process_log_file(self, all_lines):
        times = {}
        prev_task = None
        prev_ts = None
        timest = ""
        task = ""
        for line in all_lines:
            timest, task = line.split(',')
            if prev_task != None:
                diff_ts = float(timest) - float(prev_ts)
                try:
                    times[prev_task] += diff_ts
                except KeyError:
                    times[prev_task] = diff_ts
            prev_task = task[1:]
            prev_ts = timest
        # We've processed all but the last task, do that manually
        try:
            times[task[1:]] += time.time() - float(prev_ts)
        except KeyError:
            times[task[1:]] = time.time() - float(prev_ts)
        for k, v in times.iteritems():
            times[k] = v / 3600 # change sec -> hours
        return times

    """ Given a y/m/d, return back a list of datetimes that comprise the week """
    def _find_week(self, daymonthyear):
        # Weeks start on Sunday.  Always.
        datesplit = daymonthyear.split('/')
        day = datesplit[0]
        month = datesplit[1]
        year = datesplit[2]
        daylist = []
        oneday = datetime.timedelta(days=1)
        supplieddate = datetime.datetime(int(year), int(month), int(day))
        tomorrow = supplieddate
        daylist.append(supplieddate)
        while supplieddate.weekday() != calendar.SUNDAY:
            supplieddate -= oneday
            daylist.append(supplieddate)
        while tomorrow.weekday() != calendar.SATURDAY:
            tomorrow += oneday
            daylist.append(tomorrow)
        return daylist

    def print_log(self):
        dirpath = self.__create_task_dir()
        all_lines = ()
        try:
            fd = open(dirpath + "log.txt", 'r')
            all_lines = fd.readlines()
            fd.close()
            print "You've been working thus,"
            print "  "+"  ".join(all_lines)
        except IOError:
            print "You haven't logged any tasks today!\n"

    def print_tasks(self, options=None):
        global my_name
        all_files = self.__list_files(self.__tms_dir, 'tasks.txt')
        for filename in all_files:
            fd = open(filename, 'r')
            all_lines = fd.readlines()
            fd.close()
        task_list = self.get_tasks()
        task_list.sort()
        if options == "--tree":
            prev = "banana"
            for t in task_list:
                proj, rest = t.split('/')
                if proj != prev:
                    prev = proj
                    print proj + ' +-> ' + rest
                else:
                    i = 0
                    pad = ""
                    while i < len(proj):
                        i = i + 1
                        pad += ' '
                    print pad + ' |-> ' + rest
        else:
            if options != None:
                print "Unknown option: \"" + options + "\""
            else:
                # No options
                print "\n".join(task_list)

    def get_tasks(self):
        global my_name
        all_tasks = []
        removed_tasks = []
        final_task_list = []
        all_files = self.__list_files(self.__tms_dir, 'tasks.txt')
        for filename in all_files:
            fd = open(filename, 'r')
            all_lines = fd.readlines()
            fd.close()
            for t in all_lines:
                # TODO: Add each projtask to a list, checking for -projtasks
                # if one exists, remve it from the existing list
                if t[0] == '-':
                    removed_tasks.append(t[1:-1])
                else:
                    all_tasks.append(t[:-1])
        # Use sets to remove the unwanted tasks
        all_tasks_set = set(all_tasks)
        removed_tasks_set = set(removed_tasks)
        final_tasks_set = all_tasks_set.difference(removed_tasks_set)
        return list(final_tasks_set)

    """ Edit today's log file, using the user's preference if possible """
    def edit_todays_log(self):
        dirpath = self.__create_task_dir()
        editor = "/usr/bin/vi" # default
        if os.environ.has_key('EDITOR'):
            editor = os.environ['EDITOR']
        os.system(editor + " " + dirpath + "log.txt")

    # Switch to task only if it exists
    def switch_to_task(self, task):
        all_tasks = self.get_tasks()
        if task in all_tasks:
            self._write_to_file('log.txt', 'a', self._timestamp_string(task) + "\n")
        else:
            print "*** You can't switch to an unknown task ***"
            print "Available tasks are:",
            print "\n  ".join(all_tasks)

    # add a stop marker to say that we don't want to log anymore
    def add_stop_marker(self):
        print "No longer recording your tasks"
        retval = self._write_to_file('log.txt', 'a', self._timestamp_string('STOP') + "\n")
        if retval != None:
            print "*** Problem adding new task"

    # Add task to list of tasks known about
    # TODO: Need to prevent adding a task that already there, but eclipsed
    # by a del_task
    def add_task(self, projtask):
        projtasklist = projtask.split('/')
        print "Adding task \"" + ' / '.join(projtasklist) + "\""
        retval = self._write_to_file('tasks.txt', 'a', projtask + "\n")
        if retval != None:
            print "*** Problem adding new task"

    # Add task to list of tasks known about
    def del_task(self, projtask):
        projtasklist = projtask.split('/')
        print "Deleting task \"" + ' / '.join(projtasklist) + "\""
        retval = self._write_to_file('tasks.txt', 'a', "-" + projtask + "\n")
        if retval != None:
            print "*** Problem deleting task"

    def __create_task_dir(self):
        current_time = datetime.datetime.now()
        try:
            dirpath = self.__tms_dir + current_time.strftime("%Y/%m/%d/")
            os.makedirs(dirpath, mode=0777)
        except:
            pass # Do nothing
        return dirpath

    def _timestamp_string(self, s):
        return (str(time.time()) + ", " + s)

    # Write to the suplied file in today's directory
    def _write_to_file(self, filename, mode, s):
        dirpath = self.__create_task_dir()
        fd = open(dirpath + filename, mode)
        nbytes = fd.write(s)
        fd.close()
        return nbytes

    def __list_files(self, root, patterns="*", recurse=1, return_folders=0):
        """ Lifted from The Python Cookbook, 1st Ed
        Recipe 4.18 "Walking Directory Trees" """
        pattern_list = patterns.split(";")
        class Bunch:
            def __init__(self, **kwds): self.__dict__.update(kwds)
        arg = Bunch(recurse=recurse, pattern_list=pattern_list,
            return_folders=return_folders, results=[])
        def visit(arg, dirname, files):
            for name in files:
                fullname = os.path.normpath(os.path.join(dirname, name))
                if arg.return_folders or os.path.isfile(fullname):
                    for pattern in arg.pattern_list:
                        if fnmatch.fnmatch(name, pattern):
                            arg.results.append(fullname)
                            break
            if not arg.recurse: files[:]=[]
        os.path.walk(root, visit, arg)
        return arg.results



#
# Functions
#

def _main(args):
    # config file reading
#    try: 
#        fd = open(__tms_config_file, 'r')
#        config = pickle.load(fd)
#        fd.close()
#    except:
#        # Do nothing
#        pass
    # Create the tms object
    tms = TMS()
    # Parse cmd line params
    if len(args) == 3 and args[1] == "add":
        tms.add_task(args[2])
    elif len(args) == 3 and args[1] == "del":
        tms.del_task(args[2])
    elif len(args) == 3 and args[1] == "task":
        tms.switch_to_task(args[2])
    elif len(args) == 2 and args[1] == "log":
        tms.print_log()
    elif len(args) == 2 and args[1] == "stop":
        tms.add_stop_marker()
    elif len(args) == 2 and args[1] == "weekly":
        tms.print_weekly_report()
    elif len(args) == 2 and args[1] == "current":
        tms.print_current_task()
    elif len(args) == 2 and args[1] == "version":
        tms.print_version()
    elif len(args) == 2 and args[1] == "edit":
        tms.edit_todays_log()
    elif len(args) == 2 and args[1] == "today":
        tms.print_today_report()
    elif (len(args) == 2 or len(args) ==3) and args[1] == "list":
        if len(args) == 2:
            tms.print_tasks()
        elif len(args) == 3:
            tms.print_tasks(args[2])
    elif len(args) == 1 or args[1] == "help" or args[1] == "--help" or args[1] == "usage" or args[1] == "-h":
        tms.print_usage()
    else:
        print "Unknown command or option specified..."
        tms.print_usage()

if __name__ == '__main__':
    my_name = os.path.basename(sys.argv[0])
    result = _main(sys.argv)
    if result != None:
        print result


