import plugins
from settings import Settings
import irc.client
from Queue import Queue, Empty
import traceback
import time
import re
import datetime
import functools
import threading
import sqlite3
from collections import *

import os.path
from types import ModuleType

import ctypes
import shlex

def _terminate_thread(thread):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(SystemExit)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread.ident), exc)
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

def _gather(module, modules):
    if module in modules:
        return

    try:
        path = os.path.dirname(module.__file__)
    except:
        return

    if path.startswith(os.path.dirname(__file__)) and os.path.exists(path):
        modules.add(module)
        for attribute_name in dir(module):
            attribute = getattr(module, attribute_name)
            if type(attribute) is ModuleType:
                _gather(attribute, modules)


class TehbotImpl:
    def __init__(self, tehbot):
        self.core = tehbot
        self.dispatcher = Dispatcher(self)
        self.handlers = []
        self.cmd_handlers = {}
        self.channel_handlers = []
        self.channel_join_handlers = []
        self.pollers = []
        self.announcers = []
        self.queue = Queue(maxsize=0)
        self.workers = []
        self.quit_called = False
        self.dbfile = os.path.join(os.path.dirname(__file__), "../data/tehbot.sqlite")
        self.dbconn = sqlite3.connect(self.dbfile)
        self.mainqueue = Queue()
        self.privusers = defaultdict(set)
        self.privqueue = defaultdict(Queue)
        self.settings = Settings()
        self.settings.load(self.dbconn)

    def collect_plugins(self):
        plugins.collect()
        print " command handlers:", sorted(self.cmd_handlers)
        print " channel handlers:", sorted(h.__class__.__name__ for h in self.channel_handlers)
        print "chn join handlers:", sorted(h.__class__.__name__ for h in self.channel_join_handlers)
        print "          pollers:", sorted(h.__class__.__name__ for h in self.pollers)
        print "       announcers:", sorted(h.__class__.__name__ for h in self.announcers)

    def deinit(self):
        self.core.reactor.remove_global_handler("all_events", self.dispatcher.dispatch)

        for h in self.pollers + self.announcers:
            h.quit = True

        with self.core.reactor.mutex:
            for cmd in self.core.reactor.scheduler.queue[:]:
                try:
                    if cmd.target.im_func.func_name == "callme":
                        print "removing", cmd.target
                        self.core.reactor.scheduler.queue.remove(cmd)
                except:
                    pass

        self.dbconn.close()
        self.quit_called = True

    def postinit(self):
        self.core.reactor.add_global_handler("all_events", self.dispatcher.dispatch, -10)

        for p in self.pollers:
            p.schedule(20)

        for a in self.announcers:
            today = datetime.date.today()
            at = int(today.strftime("%s")) + a.at()
            if at < time.time():
                at += int(datetime.timedelta(days=1).total_seconds())
            print "Scheduling %s at %d" % (a.__class__.__name__, at)
            a.schedule(at)

        self.start_workers()

    def gather_modules(self):
        m
odules = set()
        _gather(plugins, modules)
        modules.remove(plugins)
        return modules

    def _process(self):
        dbconn = sqlite3.connect(self.dbfile)

        while True:
            try:
                plugin, args 
= self.queue.get(timeout=1)
                plugin.handle(*args, dbconn=dbconn)
                self.queue.task_done()
            except Empty:
                pass
            except SystemExit:
                raise
            except:
                traceback.print_exc()

            if self.quit_called:
                break

        dbconn.close()

    def start_workers(self):
        while len(self.workers) < self.settings.value("nr_worker_threads"):
            worker = threading.Thread(target=self._process)
            self.workers.append(worker)
            worker.start()

    def _kill_workers(self):
        while self.workers:
            _terminate_thread(self.workers.pop())

    def _get_connection(self, name):
        for c in self.core.reactor.connections:
            if c.name == name:
                return c
        return None

    def connect(self):
        for name in self.settings.connections():
            conn = self._get_connection(name)
            if conn is None:
                print "Connecting to %s" % name
                conn = self.core.reactor.server()
                conn.name = name
                conn.channels = set()
                conn.locks = dict()
                conn.tehbot_users = dict()
                self.reconnect(conn)
            else:
                self.dispatcher.join_channels(conn)

    def reconnect(self, connection):
        params = self.settings.connection_params(connection)

        if params["ssl"]:
            import ssl
            factory = irc.client.connection.Factory(wrapper=ssl.wrap_socket)
        else:
            factory = irc.client.connection.Factory()
        botname = self.settings.value("botname", connection)
        username = self.settings.value("username", connection)
        ircname = self.settings.value("ircname", connection)
        connection.connect(params["host"], params["port"], botname, params.get("password", None), username, ircname, factory)
        connection.set_rate_limit(2)
        connection.set_keepalive(60)

    def process_once(self, timeout):
        self.core.reactor.process_once(timeout)

        try:
            plugin, args = self.mainqueue.get(False)
            plugin.handle(*args, dbconn=self.dbconn)
            self.mainqueue.task_done()
        except Empty:
            pass
        except:
            traceback.print_exc()

    def quit(self, msg=None):
        print "quit called"
        self.quit_called = True
        self.core.reactor.disconnect_all(msg or "bye-bye")

    def reload(self):
        self.core.reload()

    def finalize(self):
        self.core.finalize()
        self.connect()

    def kbd_reload(self, args):
        self.reload()
        self.finalize()

    def kbd_quit(self, args):
        self.quit(args)

    def kbd_stats(self, args):
        print "Connections"
        for c in self.core.reactor.connections:
            print " * %s: %s" % (c.name, "connected" if c.is_connected() else "not connected")

        print "Delayed Commands"
        for c in self.core.reactor.scheduler.queue:
            print " * %s at %s" % (c.target.func if isinstance(c.target, functools.partial) else c.target, c)

        print "Privileged Users"
        for c, nicks in self.privusers.items():
            print " * %s: %r" % (c.name, sorted(nicks))

    def kbd_config(self, args):
        arr = shlex.split(args or "")
        if not arr:
            return
        print self.config(arr[0], arr[1:], self.dbconn)

    def config(self, handler, arr, dbconn):
        if handler == "tehbot":
            if arr[0] == "connections":
                if arr[1] == "add":
                    name = arr[2]

                    if name in self.settings.connections():
                        return "That name already exists!"

                    self.settings["connections"][name] = {
                            "host" : None,
                            "port" : 6667,
                            "ssl" : False,
                            "channels" : [ ],
                            "operators" : [ ]
                   