"""
Countdown - track and display time until events
Copyright (C) 2010  Brian S. Stephan

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.

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, see <http://www.gnu.org/licenses/>.

"""

from datetime import datetime

from dateutil.parser import *
from dateutil.relativedelta import *
from dateutil.tz import *
import MySQLdb as mdb

from extlib import irclib

from Module import Module

class Countdown(Module):

    """Track when events will happen."""

    def db_init(self):
        """Set up the database tables, if they don't exist."""

        version = self.db_module_registered(self.__class__.__name__)
        if (version == None):
            db = self.get_db()
            try:
                version = 1
                cur = db.cursor(mdb.cursors.DictCursor)
                cur.execute('''
                    CREATE TABLE countdown_item (
                        name VARCHAR(255) NOT NULL,
                        source VARCHAR(255) NOT NULL,
                        time TIMESTAMP NOT NULL,
                        PRIMARY KEY (name, source)
                    ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci
                    ''')

                db.commit()
                self.db_register_module_version(self.__class__.__name__, version)
            except mdb.Error as e:
                db.rollback()
                self.log.error("database error trying to create tables")
                self.log.exception(e)
                raise
            finally: cur.close()

    def register_handlers(self):
        """Hook handler functions into the IRC library."""

        # register IRC regex handlers
        self.irc.add_global_regex_handler('pubmsg',
                                          r'^!countdown\s+add\s+(\S+)\s+(.*)$',
                                          self.add_item)
        self.irc.add_global_regex_handler('privmsg',
                                          r'^!countdown\s+add\s+(\S+)\s+(.*)$',
                                          self.add_item)
        self.irc.add_global_regex_handler('pubmsg',
                                          r'^!countdown\s+remove\s+(\S+)$',
                                          self.remove_item)
        self.irc.add_global_regex_handler('privmsg',
                                          r'^!countdown\s+remove\s+(\S+)$',
                                          self.remove_item)
        self.irc.add_global_regex_handler('pubmsg',
                                          r'^!countdown\s+list$',
                                          self.list_items)
        self.irc.add_global_regex_handler('privmsg',
                                          r'^!countdown\s+list$',
                                          self.list_items)
        self.irc.add_global_regex_handler('pubmsg',
                                          r'^!countdown\s+(\S+)$',
                                          self.item_detail)
        self.irc.add_global_regex_handler('privmsg',
                                          r'^!countdown\s+(\S+)$',
                                          self.item_detail)

    def unregister_handlers(self):
        """Unhook handler functions from the IRC library."""

        # register IRC regex handlers
        self.irc.remove_global_regex_handler('pubmsg',
                                             r'^!countdown\s+add\s+(\S+)\s+(.*)$',
                                             self.add_item)
        self.irc.remove_global_regex_handler('privmsg',
                                             r'^!countdown\s+add\s+(\S+)\s+(.*)$',
                                             self.add_item)
        self.irc.remove_global_regex_handler('pubmsg',
                                             r'^!countdown\s+remove\s+(\S+)$',
                                             self.remove_item)
        self.irc.remove_global_regex_handler('privmsg',
                                             r'^!countdown\s+remove\s+(\S+)$',
                                             self.remove_item)
        self.irc.remove_global_regex_handler('pubmsg',
                                             r'^!countdown\s+list$',
                                             self.list_items)
        self.irc.remove_global_regex_handler('privmsg',
                                             r'^!countdown\s+list$',
                                             self.list_items)
        self.irc.remove_global_regex_handler('pubmsg',
                                             r'^!countdown\s+(\S+)$',
                                             self.item_detail)
        self.irc.remove_global_regex_handler('privmsg',
                                             r'^!countdown\s+(\S+)$',
                                             self.item_detail)

    def add_item(self, nick, userhost, event, from_admin, groups):
        """Add a new item to track, and the datetime it occurs.

        Args:
            nick        source nickname (unused)
            userhost    source userhost (unused)
            event       IRC event, target used to associate item to a source
            from_admin  whether or not the event came from an admin (unused)
            groups      tuple length 2, the item name and the time of the item

        """

        try:
            name, time_str = groups
            time = parse(time_str, default=datetime.now().replace(tzinfo=tzlocal()))

            # determine if source is a channel of a privmsg
            source = event.target()
            if source == self.irc.server.get_nickname():
                source = irclib.nm_to_n(event.source())

            self._add_countdown_item_to_database(name, source,
                                                 time.astimezone(tzutc()))
            self.log.debug("added countdown item '{0:s}' at "
                           "{1:s}".format(name, time.isoformat()))
            replystr = "added countdown item '{0:s}'".format(name)
            return self.irc.reply(event, replystr)
        except Exception as e:
            self.log.error("could not add countdown item")
            self.log.exception(e)
            return self.irc.reply(event, ("could not add countdown item: "
                                          "{0:s}".format(str(e))))

    def remove_item(self, nick, userhost, event, from_admin, groups):
        """State when the provided item will occur.

        Args:
            nick        source nickname (unused)
            userhost    source userhost (unused)
            event       IRC event, target used to filter item names
            from_admin  whether or not the event came from an admin (unused)
            groups      tuple length 1, the item name to remove

        """

        try:
            name = groups[0]

            # determine if source is a channel of a privmsg
            source = event.target()
            if source == self.irc.server.get_nickname():
                source = irclib.nm_to_n(event.source())

            self._remove_countdown_item_from_database(name, source)
            replystr = "removed countdown item '{0:s}'".format(name)
            return self.irc.reply(event, replystr)
        except Exception: pass

    def list_items(self, nick, userhost, event, from_admin, groups):
        """State when the provided item will occur.

        Args:
            nick        source nickname (unused)
            userhost    source userhost (unused)
            event       IRC event, target used to filter item names
            from_admin  whether or not the event came from an admin (unused)
            groups      empty tuple (unused)

        """

        try:
            # determine if source is a channel of a privmsg
            source = event.target()
            if source == self.irc.server.get_nickname():
                source = irclib.nm_to_n(event.source())

            cdlist = self._list_countdown_items(source)
            print(cdlist)
            liststr = "countdown items: {0:s}".format(", ".join(cdlist))
            return self.irc.reply(event, liststr)
        except Exception: pass

    def item_detail(self, nick, userhost, event, from_admin, groups):
        """State when the provided item will occur.

        Args:
            nick        source nickname (unused)
            userhost    source userhost (unused)
            event       IRC event, target used to filter item names
            from_admin  whether or not the event came from an admin (unused)
            groups      tuple length 1, the item name to look up

        """

        try:
            name = groups[0]

            # determine if source is a channel of a privmsg
            source = event.target()
            if source == self.irc.server.get_nickname():
                source = irclib.nm_to_n(event.source())

            time = self._get_countdown_item_time(name, source)
            if time:
                rdelta = relativedelta(time, datetime.now().replace(tzinfo=tzlocal()))
                relstr = "{0:s} will occur in ".format(name)
                if rdelta.years != 0:
                    relstr += "{0:s} year{1:s} ".format(str(rdelta.years),
                                                        "s" if rdelta.years != 1 else "")
                if rdelta.months != 0:
                    relstr += "{0:s} month{1:s}, ".format(str(rdelta.months),
                                                          "s" if rdelta.months != 1 else "")
                if rdelta.days != 0:
                    relstr += "{0:s} day{1:s}, ".format(str(rdelta.days),
                                                        "s" if rdelta.days != 1 else "")
                if rdelta.hours != 0:
                    relstr += "{0:s} hour{1:s}, ".format(str(rdelta.hours),
                                                         "s" if rdelta.hours != 1 else "")
                if rdelta.minutes != 0:
                    relstr += "{0:s} minute{1:s}, ".format(str(rdelta.minutes),
                                                           "s" if rdelta.minutes != 1 else "")
                if rdelta.seconds != 0:
                    relstr += "{0:s} second{1:s}, ".format(str(rdelta.seconds),
                                                           "s" if rdelta.seconds != 1 else "")
                # remove trailing comma from output
                return self.irc.reply(event, relstr[0:-2])
        except mdb.Error: pass

    def _add_countdown_item_to_database(self, name, source, time):
        """Add the given countdown item from the given source to the database.

        Args:
            name        the name of the item to add
            source      the source (nick or channel) this item is from
            time        the datetime of the item to add

        """

        db = self.get_db()
        try:
            cur = db.cursor(mdb.cursors.DictCursor)
            statement = '''
                INSERT INTO countdown_item (name, source, time)
                  VALUES (%s, %s, %s)
                  ON DUPLICATE KEY UPDATE time=%s
                '''
            cur.execute(statement, (name, source, time, time))
            db.commit()
            return cur.lastrowid
        except mdb.Error as e:
            db.rollback()
            self.log.error("database error during adding countdown item")
            self.log.exception(e)
            raise
        finally: cur.close()

    def _remove_countdown_item_from_database(self, name, source):
        """Add the given countdown item from the given source to the database.

        Args:
            name        the name of the item to remove
            source      the source (nick or channel) this item is from

        """

        db = self.get_db()
        try:
            cur = db.cursor(mdb.cursors.DictCursor)
            statement = '''
                DELETE FROM countdown_item WHERE name = %s
                  AND source = %s
                '''
            cur.execute(statement, (name, source))
            db.commit()
            return
        except mdb.Error as e:
            db.rollback()
            self.log.error("database error during removing countdown item")
            self.log.exception(e)
            raise
        finally: cur.close()

    def _list_countdown_items(self, source):
        """Retrieve the names of all countdown items in the database.

        Args:
            source      the source (nick or channel) to retrieve items for

        Returns:
            The names of the items, as a list of strings.

        """

        items = []
        db = self.get_db()
        try:
            cur = db.cursor(mdb.cursors.DictCursor)
            query = '''
                SELECT name FROM countdown_item
                  WHERE source = %s
                '''
            cur.execute(query, (source,))
            results = cur.fetchall()
            for result in results:
                items.append(result['name'])

            return items
        except mdb.Error as e:
            self.log.error("database error while getting item time")
            self.log.exception(e)
            raise
        finally: cur.close()

    def _get_countdown_item_time(self, name, source):
        """Add the given countdown item from the given source to the database.

        Args:
            name        the name of the item to add
            source      the source (nick or channel) this item is from

        Returns:
            The datetime for the item in local time.

        """

        db = self.get_db()
        try:
            cur = db.cursor(mdb.cursors.DictCursor)
            query = '''
                SELECT time FROM countdown_item
                  WHERE name = %s AND source = %s
                '''
            cur.execute(query, (name, source))
            result = cur.fetchone()
            if result:
                return result['time'].replace(tzinfo=tzutc())
        except mdb.Error as e:
            self.log.error("database error while getting item time")
            self.log.exception(e)
            raise
        finally: cur.close()

# vi:tabstop=4:expandtab:autoindent
# kate: indent-mode python;indent-width 4;replace-tabs on;