import BaseHTTPServer
from narval.utils.BinaryStore import BinaryStore
from ProtocolHandler import *
from threading import Thread, Lock, Semaphore, Event
from narval.lib import select_one, get_binary_store
from narval import config
import os
from narval.lib import *

""" HttpProtocolHandler is the gateway between Narval and a Http server.
    It has three classes:
    - HttpProtocolHandler
    - HttpService
    - ReqeustHandler
"""

emptyImgPath = os.path.abspath(config.get_home())+"/data/tr.png"
Identifier_Lock = Lock()
id = 0
def make_new_id():
    global id
    Identifier_Lock.acquire()
    id +=1
    Identifier_Lock.release()
    return str(id)

class HttpProtocolHandler(ProtocolHandler):
    def __init__(self,narval):
        ProtocolHandler.__init__(self)
        
        self.documentation = 'An HTTP server delegating operation to Narval'
        self.prototype = (('port on which the server listens, recipe to instantiate',
                           ('http-server[@port][@recipe]',)),
                          ('response to an HTTP request',
                           ('http-response[@unique-id]/content',)
                           ),
                          ('HTTP request',
                           ('http-request[@type]',
                            'http-request[@http-version]',
                            'http-request/url/text()',
                            'http-request/data',
                            'http-request/headers/header/text()|http-request/headers/text()',
                            'http-request[@unique-id]')
                           )
                          )
        self.eid_activator = {}
        self.activations = {}
        self.pending_requests = {}
        self.narval = narval
    
    def activate(self,activator):
        # get informations in xml_string activator
        dom = self.build_dom(activator)
        node = dom.documentElement
        eid = node.getAttributeNS(AL_NS,'eid')
        port = node.getAttributeNS(NO_NS,'port')
        recipe = node.getAttributeNS(NO_NS,'recipe')
        self.eid_activator[eid] = port

        if self.activations.has_key(port):
            raise HandlerAlreadyActivated(port)

        # create and launch service
        service = HttpService(('',int(port)),self, recipe)
        thread = Thread(target = service.serve_forever, name='HttpService_%s'%port)
        self.activations[port] = (service,thread)
        thread.start()
        
    def deactivate(self, eid):
        try:
            port = self.eid_activator[eid]
        except KeyError:
            raise HandlerNotActivated(eid)
        service,thread = self.activations[port]
        service.running = None
        
    def stop(self):
        import socket
        print self.activations
        for (port,(service,thread)) in self.activations.items():
            service.running = None
            # we have to connect to the socket so that the service will actually stop
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect(('', int(port)))
            s.send('stop')
            s.close()
            thread.join()


    def handleOutgoing(self,arguments):
        dom = self.build_dom(arguments)
        uniqueid = dom.documentElement.getAttributeNS(NO_NS,'unique-id')
        # find the request associated with this answer
        communication_list = self.pending_requests[uniqueid]
        # get the lock (a thread is waiting on this lock)
        # put the dom tree in the communication list, and release the lock
        lock = communication_list[0]
        communication_list[0]=dom
        lock.set()
    
class HttpService( BaseHTTPServer.HTTPServer):
    """ This class uses methods of HTTPServer, redefining
        the method process_request that is called by handle_request()
        in the derivater class
    """

    def __init__(self,server_addr,protocol_handler, recipe, max_threads=8):
        BaseHTTPServer.HTTPServer.__init__(self,server_addr,RequestHandler)
        self.protocol_handler = protocol_handler
        self.recipe = recipe
        self.thread_sem = Semaphore(max_threads)
        self.running = 1

    def process_request(self, request, client_address):
        """Start a new thread to process the request."""
        # this code comes from the ThreadingMixin in SocketService.py
        # finish_request instantiate the RequestHandler Class
        is_thread = 1
        self.thread_sem.acquire()
        t = Thread(target = self.finish_request,
                             args = (request, client_address))
        t.start()

    def serve_forever(self):
        """Handle one request at a time until doomsday."""
        # overriden so that we can stop the service when deactivate is called
        while self.running:
            print self,'handle_request'
            self.handle_request()

    def finish_request(self, request, client_address):
        """ instantiate the RequestHandler class """
        try:
            self.RequestHandlerClass(request, client_address, self)
            self.close_request(request)
        except:
            import traceback
            traceback.print_exc()


    def verify_request(self,r,addr):
        return self.running is not None

    def handle_request(self):
        """ copied from SocketServer module """
        import socket
        try:
            request, client_address = self.get_request()
        except socket.error:
            print 'SOCKET ERROR; could not get request'
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except:
                self.handle_error(request, client_address)


class  RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    def do_GET(self):
        print 'do GET'
        self._process_request()

    def do_POST(self):
        print 'do POST'
        self._process_request()

    def _process_request(self):
        """ process and give narval the request and the associated recipe"""
        server = self.server
        # generate a unique id used to recognize the response generated by Narval
        # the exchange medium is a dictionnary. Accesses to dictionaries are
        # atomic, so we don't need to lock them.
        # We associate the uid with a list containing a lock that will be released()
        # when an answer is received. We expect the list to hold the answer by then.
        # The code performing the operation is HttpProtocolHandler.handleOutgoing()
        uniqueid = make_new_id()
        sem = Event()
        communication_list = [sem]
        server.protocol_handler.pending_requests[uniqueid] = communication_list
        try:
            try:
                request = self._build_xml_request(uniqueid)
                # start the plan
                server.protocol_handler.narval.instantiateHandlerRecipe(
                    self.server.recipe, 
                    request,
                    server.protocol_handler
                    )
                # waiting for Narval to process the request (with handleOutgoing)
                # FIXME: this timeout is a temporary solution for cases when
                # narval crashes?
                sem.wait(360)
                dom = communication_list[0]
                # send the answer
                if dom==sem:
                # FIXME: this errorhandling is maladroit
                    status = '500' # right number?
                    data   = 'Sorry, Narval could not satisfy your request'
                    content_type = 'text/plain'
                
                else:
                    data, content_type, status = _construct_response(dom)

                # send response to http-client
                self.send_response(status)
                self.send_header('Content-Type',content_type)
                self.end_headers()
                self.wfile.write(str(data)) # add encode charset?
#                self.send_response(data)
            except:
                import traceback
                traceback.print_exc()
                self.send_response(500)
                self.end_headers()
                raise
        finally:
            del server.protocol_handler.pending_requests[uniqueid]
            server.thread_sem.release()
            
    def _build_xml_request(self,uniqueid):
        """ build the http-request """
        import re
        headers = ['<header name=%s>%s</header>'%(
                    repr(k.lower()), 
                    v.replace("&","&amp;")) for (k,v) in self.headers.items()]
        if self.command=='POST':
            data = self.rfile.read()
        else:
            data = ' '

        path = self.path.replace("&","&amp;")
        request="<http-request unique-id='%s' type=%s http-version=%s><url>%s</url><headers>%s</headers><data>%s</data></http-request>"%\
                 (uniqueid,
                  repr(self.command),
                  repr(self.request_version),
                  path,
                  ''.join(headers),
                  data)
        return request

def _get_charset(content_type):
    """if charset is defined in header "content-type", we get it
       to avoid errors, set a charset if not specified
    """
    import re
    charset = re.match( re.compile(".*charset=(.*)"), content_type)
    if not charset:
        print "No charset: utf-8 default"
        return "utf-8"
    else: 
        return charset.group(1)

def _construct_response(dom):
    """construct the http-response given its dom representation
    """
    status = dom.documentElement.getAttributeNS(NO_NS,'status')
    try:
        content_type = select_one(
        dom, '//http-response/headers/header[@name="Content-Type"]/text()').data
    except:
        content_type='text/html ; charset=utf-8'

    # if sent data is image
    if content_type[:5] == 'image':

        ref = select_one(dom, '//http-response/reference')
        typeref = ref.getAttributeNS(NO_NS, 'type')
        link = select_one(dom, '//http-response/reference/text()').data

        if typeref == 'url':
            data = get_binary_store().get_binary_from_url(link)
        else: # it must be id 
            data = get_binary_store().get_binary_from_id(link)

    # if we have to process text
    elif content_type[:4] == 'text':
        charset = _get_charset(content_type)
        try:
            data = select_one(dom, 
            "//http-response/content/text()").data
        except:# is this clever..²
            try:
                data = select_one(dom, 
                "//http-response/errmsg/text()").data
            except:
                data = "Could not check error message"

        data = data.encode(charset)
    else:
        print "Warning, content-type value =", content_type
        # fixme...un peu crado les charset default partout
        charset = 'utf-8'
        try:
            data = select_one(dom, 
            "//http-response/content/text()").data
        except:# is this clever..²
            data = 'javascript_data...'


    return (data, content_type, status)

