#!/usr/bin/env python
# - coding: utf-8 -

# Copyright (C) 2010 Matías Ribecky <matias at mribecky.com.ar>
# Copyright (C) 2010-2012 Toms Bauģis <toms.baugis@gmail.com>
# Copyright (C) 2012 Ted Smith <tedks at cs.umd.edu>

# This file is part of Project Hamster.

# Project Hamster 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.

# Project Hamster 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 Project Hamster.  If not, see <http://www.gnu.org/licenses/>.

'''A script to control the applet from the command line.'''

import sys, os
import optparse
import re
import datetime as dt

from hamster import client, reports
from hamster.lib import Fact, stuff


def word_wrap(line, max_len):
    """primitive word wrapper"""
    lines = []
    cur_line, cur_len = "", 0
    for word in line.split():
        if len("%s %s" % (cur_line, word)) < max_len:
            cur_line = ("%s %s" % (cur_line, word)).strip()
        else:
            if cur_line:
                lines.append(cur_line)
            cur_line = word
    if cur_line:
        lines.append(cur_line)
    return lines


def fact_dict(fact_data, with_date):
    fact = {}
    if with_date:
        fmt = '%Y-%m-%d %H:%M'
    else:
        fmt = '%H:%M'

    fact['start'] = fact_data.start_time.strftime(fmt)
    if fact_data.end_time:
        fact['end'] = fact_data.end_time.strftime(fmt)
    else:
        end_date = dt.datetime.now()
        fact['end'] = ''

    fact['duration'] = stuff.format_duration(fact_data.delta)

    fact['activity'] = fact_data.activity
    fact['category'] = fact_data.category
    if fact_data.tags:
        fact['tags'] = ' '.join('#%s' % tag for tag in fact_data.tags)
    else:
        fact['tags'] = ''

    fact['description'] = fact_data.description

    return fact


_DATETIME_PATTERN = ('^((?P<relative>-\d.+)?|('
                     '(?P<date1>\d{4}-\d{2}-\d{2})?'
                     '(?P<time1> ?\d{2}:\d{2})?'
                     '(?P<dash> ?-)?'
                     '(?P<date2> ?\d{4}-\d{2}-\d{2})?'
                     '(?P<time2> ?\d{2}:\d{2})?)?)'
                     '(?P<rest>\D.+)?$')
_DATETIME_REGEX = re.compile(_DATETIME_PATTERN)
def parse_datetime_range(arg):
    '''Parse a date and time.'''
    match = _DATETIME_REGEX.match(arg)
    if not match:
        return None, None

    fragments = match.groupdict()
    rest = (fragments['rest'] or '').strip()

    # bail out early on relative minutes
    if fragments['relative']:
        delta_minutes = int(fragments['relative'])
        return dt.datetime.now() - dt.timedelta(minutes=delta_minutes), rest

    start_time, end_time = None, None

    def to_time(timestr):
        timestr = (timestr or "").strip()
        try:
            return dt.datetime.strptime(timestr, "%Y-%m-%d").date()
        except ValueError:
            try:
                return dt.datetime.strptime(timestr, "%H:%M").time()
            except ValueError:
                pass
        return None


    if fragments['date1'] or fragments['time1']:
        start_time = dt.datetime.combine(to_time(fragments['date1']) or dt.date.today(),
                                         to_time(fragments['time1']) or dt.time())
    if fragments['date2'] or fragments['time2']:
        end_time = dt.datetime.combine(to_time(fragments['date2']) or start_time or dt.date.today(),
                                       to_time(fragments['time2']) or dt.time())

    return start_time, end_time



class HamsterClient(object):
    '''The main application.'''
    def __init__(self):
        self.storage = client.Storage()

    def today(self, *args):
        """launches today's view"""
        import gtk, glib
        glib.set_prgname(unicode(_("Time Tracker")).encode("utf-8"))

        from hamster import today
        app = today.DailyView()
        gtk.main()

    def _launch_window(self, window_name):
        import dbus
        bus = dbus.SessionBus()
        server = bus.get_object("org.gnome.Hamster.WindowServer",
                                "/org/gnome/Hamster/WindowServer")
        getattr(server, window_name)()

    def overview(self, *args):
        self._launch_window("overview")

    def statistics(self, *args):
        self._launch_window("statistics")

    def about(self, *args):
        self._launch_window("about")


    def assist(self, *args):
        assist_command = args[0] if args else ""

        if assist_command == "start":
            hamster_client._activities(sys.argv[-1])
        elif assist_command == "export":
            formats = "html tsv xml ical".split()
            chosen = sys.argv[-1]
            formats = [f for f in formats if not chosen or f.startswith(chosen)]
            print "\n".join(formats)


    def toggle(self):
        self.storage.toggle()


    def track(self, *args):
        """same as start"""
        self.start(*args)


    def start(self, *args):
        '''Start a new activity.'''
        if not args:
            print "Error: please specify activity"
            return

        activity = args[0]
        start_time, end_time = parse_datetime_range(" ".join(args[1:]))
        start_time = start_time or dt.datetime.now()

        self.storage.add_fact(Fact(activity,
                                         start_time = start_time,
                                         end_time = end_time))


    def stop(self, *args):
        '''Stop tracking the current activity.'''
        self.storage.stop_tracking()


    def export(self, *args):
        args = args or []
        export_format, start_time, end_time = "html", None, None
        if args:
            export_format = args[0]
        start_time, end_time = parse_datetime_range(" ".join(args[1:]))

        start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
        end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
        facts = self.storage.get_facts(start_time, end_time)

        writer = reports.simple(facts, start_time, end_time, export_format)
        print writer.export()


    def _activities(self, search=""):
        '''Print the names of all the activities.'''
        if "@" in search:
            activity, category = search.split("@")
            for cat in self.storage.get_categories():
                if not category or cat['name'].lower().startswith(category.lower()):
                    print "%s@%s" % (activity.encode("utf8"), cat['name'].encode("utf8"))
        else:
            for activity in self.storage.get_activities(search):
                print activity['name'].encode('utf8')
                if activity['category']:
                    print '%s@%s' % (activity['name'].encode('utf8'), activity['category'].encode('utf8'))


    def activities(self, *args):
        '''Print the names of all the activities.'''
        search = args[0] if args else ""
        for activity in self.storage.get_activities(search):
            print '%s@%s' % (activity['name'].encode('utf8'), activity['category'].encode('utf8'))

    def categories(self, *args):
        '''Print the names of all the categories.'''
        for category in self.storage.get_categories():
            print category['name'].encode('utf8')


    def list(self, *times):
        """list facts within a date range"""
        start_time, end_time = parse_datetime_range(" ".join(times or []))

        start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
        end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
        self._list(start_time, end_time)


    def current(self, *args):
        """prints current activity. kinda minimal right now"""
        facts = self.storage.get_todays_facts()
        if facts and not facts[-1].end_time:
            print "%s %s" % (unicode(facts[-1]).encode("utf-8").strip(),
                             stuff.format_duration(facts[-1].delta, human=False))
        else:
            print _("No activity")


    def search(self, *args):
        """search for activities by name and optionally within a date range"""
        args = args or []
        search = ""
        if args:
            search = args[0]

        start_time, end_time = parse_datetime_range(" ".join(args[1:]))

        start_time = start_time or dt.datetime.combine(dt.date.today(), dt.time())
        end_time = end_time or start_time.replace(hour=23, minute=59, second=59)
        self._list(start_time, end_time, search)


    def _list(self, start_time, end_time, search=""):
        """Print a listing of activities"""
        facts = self.storage.get_facts(start_time, end_time, search)


        headers = {'activity': _("Activity"),
                   'category': _("Category"),
                   'tags': _("Tags"),
                   'description': _("Description"),
                   'start': _("Start"),
                   'end': _("End"),
                   'duration': _("Duration")}


        # print date if it is not the same day
        print_with_date = start_time.date() != end_time.date()

        cols = 'start', 'end', 'duration', 'activity', 'category'


        widths = dict([(col, len(headers[col])) for col in cols])
        for fact in facts:
            fact = fact_dict(fact, print_with_date)
            for col in cols:
                widths[col] = max(widths[col], len(fact[col]))

        cols = ["{{{col}: <{len}}}".format(col=col, len=widths[col]) for col in cols]
        fact_line = " | ".join(cols)

        row_width = sum([val + 3 for val in widths.values()])

        print
        print fact_line.format(**headers)
        print "-" * min(row_width, 80)

        by_cat = {}
        for fact in facts:
            cat = fact.category or _("Uncategorized")
            by_cat.setdefault(cat, dt.timedelta(0))
            by_cat[cat] += fact.delta

            pretty_fact = fact_dict(fact, print_with_date)
            print fact_line.format(**pretty_fact)

            if pretty_fact['description']:
                for line in word_wrap(pretty_fact['description'], 76):
                    print "    %s" % line

            if pretty_fact['tags']:
                for line in word_wrap(pretty_fact['tags'], 76):
                    print "    %s" % line

        print "-" * min(row_width, 80)

        cats = []
        for cat, duration in sorted(by_cat.iteritems(), key=lambda x: x[1], reverse=True):
            cats.append("%s: %s" % (cat, "%.1fh" % (stuff.duration_minutes(duration) / 60.0)))

        for line in word_wrap(", ".join(cats), 80):
            print line

        print




if __name__ == '__main__':
    from hamster.lib import i18n
    i18n.setup_i18n()

    usage = _(
"""
Actions:
    * start / track <activity> [start-time] [end-time]: Track an activity
    * stop: Stop tracking current activity.
    * list [start-time] [end-time]: List activities
    * search [terms] [start-time] [end-time]: List activities matching a search
      term
    * export [html|tsv|ical|xml] [start-time] [end-time]: Export activities with
      the specified format
    * current: Print current activity
    * activities: List all the activities names, one per line.
    * categories: List all the categories names, one per line.

    * overview / statistics / about: launch specific window

Time formats:
    * 'YYYY-MM-DD hh:mm:ss': If date is missing, it will default to today.
      If time is missing, it will default to 00:00 for start time and 23:59 for
      end time.
    * '-minutes': Relative time in minutes from the current date and time.

Example usage:
    hamster start bananas -20
        start activity 'bananas' with start time 20 minutes ago

    hamster search pancakes 2012-08-01 2012-08-30
        look for an activity matching terms 'pancakes` between 1st and 30st
        August 2012. Will check against activity, category, description and tags
""")
    hamster_client = HamsterClient()

    if len(sys.argv) < 2:
        hamster_client.today()
    else:
        command, args = sys.argv[1], sys.argv[2:]
        if hasattr(hamster_client, command):
            getattr(hamster_client, command)(*args)
        else:
            sys.exit(usage % {'prog': sys.argv[0]})
