import threading
import logging
import socket
import ssl
import binascii


class RaceConditionThread(threading.Thread):
    def __init__(self, num_thread, plugin_inst, send_last_byte, get_response):
        threading.Thread.__init__(self)
        self.daemon = True

        self.num_thread = num_thread
        self.plugin_inst = plugin_inst
        self.send_last_byte = send_last_byte
        self.get_response = get_response
        self.ready = False

    def run(self):
        try:
            request_str = self.plugin_inst.get_request_str()
        except StopIteration:
            logging.debug('Thread %s has no request to send' % self.num_thread)
            self.ready = True
            return

        try:
            conn = self.connect(self.plugin_inst.get_host(),
                                self.plugin_inst.get_port(),
                                self.plugin_inst.use_ssl())
        except Exception, e:
            logging.error('Thread %s failed to connect: "%s"' % (self.num_thread, e))
            self.ready = True
            return

        # Send the whole request, except from the last byte. The server will
        # wait for some seconds until the last byte is sent, because the content
        # length tells him there are missing bytes
        conn.send(request_str[:-1])

        args = (self.num_thread, binascii.hexlify(request_str[:-1][-10:]))
        msg = 'Thread %s last 10 bytes sent before waiting for event: "%s"'
        logging.debug(msg % args)

        # Let the core know that we're ready
        logging.debug('Thread %s is ready' % self.num_thread)
        self.ready = True

        # Wait until all threads are ready and the core signals us to continue
        self.send_last_byte.wait()

        # Send the last byte
        conn.send(request_str[-1:])
        args = (self.num_thread, binascii.hexlify(request_str[-1:]))
        logging.debug('Thread %s sent last byte: %s' % args)

        self.get_response.wait()
        response = self.recv_all(conn)
        self.plugin_inst.analyze_response(response)

        conn.close()

    def connect(self, host, port, use_ssl):
        if not use_ssl:
            return socket.create_connection((host, port))
        else:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            ssl_sock = ssl.wrap_socket(s)
            ssl_sock.connect((host, port))
            return CallWrapper(ssl_sock)

    def recv_all(self, conn):
        buff = conn.recv(2048)
        while True:
            more = conn.recv(4096)
            if not more:
                break
            else:
                buff += more
        return buff


class CallWrapper(object):
    def __init__(self, ssl_sock):
        self.ssl_sock = ssl_sock

    def send(self, *args, **kwargs):
        return self.ssl_sock.write(*args, **kwargs)

    def recv(self, *args, **kwargs):
        return self.ssl_sock.read(*args, **kwargs)

    def close(self):
        return self.ssl_sock.close()


def create_threads(num_threads, plugin_inst, send_last_byte, get_response):
    all_threads = []

    for i in xrange(num_threads):
        rct = RaceConditionThread(i, plugin_inst, send_last_byte, get_response)
        all_threads.append(rct)

    return all_threads