import re
import socket
import time
import thread
import Queue

from ssl import wrap_socket, CERT_NONE, CERT_REQUIRED, SSLError


def decode(txt):
    for codec in ('utf-8', 'iso-8859-1', 'shift_jis', 'cp1252'):
        try:
            return txt.decode(codec)
        except UnicodeDecodeError:
            continue
    return txt.decode('utf-8', 'ignore')


def censor(text):
    text = text.replace('\n', '').replace('\r', '')
    replacement = '[censored]'
    if 'censored_strings' in bot.config:
        if bot.config['censored_strings']:
            words = map(re.escape, bot.config['censored_strings'])
            regex = re.compile('({})'.format("|".join(words)))
            text = regex.sub(replacement, text)
    return text


class crlf_tcp(object):
    """Handles tcp connections that consist of utf-8 lines ending with crlf"""

    def __init__(self, host, port, timeout=300):
        self.ibuffer = ""
        self.obuffer = ""
        self.oqueue = Queue.Queue()  # lines to be sent out
        self.iqueue = Queue.Queue()  # lines that were received
        self.socket = self.create_socket()
        self.host = host
        self.port = port
        self.timeout = timeout

    def create_socket(self):
        return socket.socket(socket.AF_INET, socket.TCP_NODELAY)

    def run(self):
        noerror = 0
        while 1:
	    try:
                self.socket.connect((self.host, self.port))
                break
	    except socket.gaierror as e:
                time.sleep(5)
            except socket.timeout as e:
                time.sleep(5)

        thread.start_new_thread(self.recv_loop, ())
        thread.start_new_thread(self.send_loop, ())

    def recv_from_socket(self, nbytes):
        return self.socket.recv(nbytes)

    def get_timeout_exception_type(self):
        return socket.timeout

    def handle_receive_exception(self, error, last_timestamp):
	print("Receive exception: %s" % (error))
        if time.time() - last_timestamp > self.timeout:
            print("Receive timeout. Restart connection.")
            self.iqueue.put(StopIteration)
            self.socket.close()
            return True
        return False

    def handle_send_exception(self, error):
        print("Send exception: %s" % (error))
        self.iqueue.put(StopIteration)
        self.socket.close()
        return True

    def recv_loop(self):
        last_timestamp = time.time()
        while True:
            try:
		data = self.recv_from_socket(4096)
                self.ibuffer += data
                if data:
                    last_timestamp = time.time()
                else:
                    if time.time() - last_timestamp > self.timeout:
                        self.iqueue.put(StopIteration)
                        self.socket.close()
                        return
                    time.sleep(1)
            except (self.get_timeout_exception_type(), socket.error) as e:
                if self.handle_receive_exception(e, last_timestamp):
                    return
                continue
            except AttributeError:
                return

            while '\r\n' in self.ibuffer:
                line, self.ibuffer = self.ibuffer.split('\r\n', 1)
                self.iqueue.put(decode(line))

    def send_loop(self):
        while True:
            try:
                line = self.oqueue.get().splitlines()[0][:500]
                if line == StopIteration:
                    return
                print ">>> %r" % line
                self.obuffer += line.encode('utf-8', 'replace') + '\r\n'
                while self.obuffer:
                    sent = self.socket.send(self.obuffer)
                    self.obuffer = self.obuffer[sent:]

            except socket.error as e:
                 self.handle_send_exception(e)
                 return

class crlf_ssl_tcp(crlf_tcp):
    """Handles ssl tcp connetions that consist of utf-8 lines ending with crlf"""

    def __init__(self, host, port, ignore_cert_errors, timeout=300):
        self.ignore_cert_errors = ignore_cert_errors
        crlf_tcp.__init__(self, host, port, timeout)

    def create_socket(self):
        return wrap_socket(crlf_tcp.create_socket(self), server_side=False,
                           cert_reqs=CERT_NONE if self.ignore_cert_errors else
                           CERT_REQUIRED)

    def recv_from_socket(self, nbytes):
        return self.socket.read(nbytes)

    def get_timeout_exception_type(self):
        return SSLError

    def handle_receive_exception(self, error, last_timestamp):
        # this is terrible
        #if not "timed out" in error.args[0]:
        #    raise
        return crlf_tcp.handle_receive_exception(self, error, last_timestamp)

    def handle_send_exception(self, error):
        return crlf_tcp.handle_send_exception(self, error)


irc_prefix_rem = re.compile(r'(.*?) (.*?) (.*)').match
irc_noprefix_rem = re.compile(r'()(.*?) (.*)').match
irc_netmask_rem = re.compile(r':?([^!@]*)!?([^@]*)@?(.*)').match
irc_param_ref = re.compile(r'(?:^|(?<= ))(:.*|[^ ]+)').findall


class IRC(object):
    """handles the IRC protocol"""

    def __init__(self, name, server, nick, port=6667, channels=[], conf={}):
        self.name = name
        self.channels = channels
        self.conf = conf
        self.server = server
        self.port = port
        self.nick = nick
        self.history = {}
        self.vars = {}

        self.out = Queue.Queue()  # responses from the server are placed here
        # format: [rawline, prefix, command, params,
        # nick, user, host, paramlist, msg]
        self.connect()

        thread.start_new_thread(self.parse_loop, ())

    def create_connection(self):
        return crlf_tcp(self.server, self.port)

    def connect(self):
        self.conn = self.create_connection()
        thread.start_new_thread(self.conn.run, ())
        self.set_pass(self.conf.get('server_password'))
        self.set_nick(self.nick)
        self.cmd("USER",
                 [conf.get('user', 'cloudbot'), "3", "*", conf.get('realname',
                                                                   'CloudBot - http://git.io/cloudbot')])

    def parse_loop(self):
        while True:
            # get a message from the input queue
            msg = self.conn.iqueue.get()

            if msg == StopIteration:
                self.connect()
                continue

            # parse the message
            if msg.startswith(":"):  # has a prefix
                prefix, command, params = irc_prefix_rem(msg).groups()
            else:
                prefix, command, params = irc_noprefix_rem(msg).groups()
            nick, user, host = irc_netmask_rem(prefix).groups()
            mask = nick + "!" + user + "@" + host
            paramlist = irc_param_ref(params)
            lastparam = ""
            if paramlist:
                if paramlist[-1].startswith(':'):
                    paramlist[-1] = paramlist[-1][1:]
                lastparam = paramlist[-1]
            # put the parsed message in the response queue
            self.out.put([msg, prefix, command, params, nick, user, host,
                          mask, paramlist, lastparam])
            # if the server pings us, pong them back
            if command == "PING":
                self.cmd("PONG", paramlist)

    def set_pass(self, password):
        if password:
            self.cmd("PASS", [password])

    def set_nick(self, nick):
        self.cmd("NICK", [nick])

    def join(self, channel):
        """ makes the bot join a channel """
        self.send("JOIN {}".format(channel))
        if channel not in self.channels:
            self.channels.append(channel)

    def part(self, channel):
        """ makes the bot leave a channel """
        self.cmd("PART", [channel])
        if channel in self.channels:
            self.channels.remove(channel)

    def msg(self, target, text):
        """ makes the bot send a PRIVMSG to a target  """
        self.cmd("PRIVMSG", [target, text])

    def ctcp(self, target, ctcp_type, text):
        """ makes the bot send a PRIVMSG CTCP to a target """
        out = u"\x01{} {}\x01".format(ctcp_type, text)
        self.cmd("PRIVMSG", [target, out])

    def cmd(self, command, params=None):
        if params:
            params[-1] = u':' + params[-1]
            self.send(u"{} {}".format(command, ' '.join(params)))
        else:
            self.send(command)

    def send(self, str):
        self.conn.oqueue.put(str)


class SSLIRC(IRC):
    def __init__(self, name, server, nick, port=6667, channels=[], conf={},
                 ignore_certificate_errors=True):
        self.ignore_cert_errors = ignore_certificate_errors
        IRC.__init__(self, name, server, nick, port, channels, conf)

    def create_connection(self):
        return crlf_ssl_tcp(self.server, self.port, self.ignore_cert_errors)
