""" Composer for HTTP responses

This module contains a composer, which can compose responses to
HTTP requests from a client.
"""

from multiprocessing import Process, Queue
import re
import time

from config import config
import encodings
from message import Response
from resource import Resource, FileExistError, FileAccessError
import weblogging as logging

class ResponseComposer:
    """Class that composes a HTTP response to a HTTP request"""

    def __init__(self, timeout):
        """Initialize the ResponseComposer

        Args:
            timeout (int): connection timeout
        """
        self.timeout = timeout

    def compose_response(self, request):
        """Compose a response to a request

        Args:
            request (webhttp.Request): request from client

        Returns:
            webhttp.Response: response to request

        """
        response = Response()

        q = Queue()

        if re.search(r'\/\.\.?(\/|$)', request.uri):
            p = Process(target=self.serve_error,
                    args=(403,), kwargs=dict(queue=q, request=request))
        else:
            p = Process(target=self.serve,
                    args=(request.uri,), kwargs=dict(queue=q, request=request))

        p.start()
        p.join(self.timeout)

        if p.is_alive():
            p.terminate()
            p.join()

            logging.warning('Had to kill Composer.serve')

            resp = Response()
            resp.code = 504
            resp.set_header('Connection', 'close')
            resp.body = 'Internal timeout'
            resp.set_content_length()
        else:
            try:
                resp = q.get(False)
            except:
                # After a timeout, we just make the most minimalistic response
                # here (that is, no special error files are considered).
                resp = Response()
                resp.code = 500
                resp.set_header('Connection', 'close')
                resp.body = 'This should never happen'
                resp.set_content_length()

        return resp

    def serve(self, uri, code=200, etag=None, request=None, queue=None):
        resp = Response()
        req = request

        try:
            resource = Resource(uri)
            if req != None and (
                    resource.etag_match(req.get_header('If-None-Match')) or \
                    req.get_header('If-Match') != None and \
                    not resource.etag_match(req.get_header('If-Match'))):
                resp.code = 304
            else:
                resp.code = code
                resp.body = None
                encs = req.encodings() if req != None else [encodings.IDENTITY]
                for enc in encs:
                    try:
                        resp.body = resource.get_content(enc)
                        resp.set_header('Content-Encoding', encodings.str(enc))
                        break
                    except UnknownEncodingError:
                        pass
                if resp.body == None:
                    return self.serve_error(406, req, queue=queue)
            resp.set_header('ETag', resource.generate_etag())
            resp.set_header('Content-Type', resource.get_content_type())
            resp.set_content_length()
        except FileExistError:
            if code < 400:
                self.serve_error(404, req, queue=queue)
            else:
                resp.code = code
                resp.body = 'Error %d' % code
                resp.set_content_length()
        except FileAccessError:
            self.serve_error(403, req, queue=queue)

        conn = 'close'
        if req != None and req.get_header('Connection') == 'keep-alive':
            conn = 'keep-alive'
        resp.set_header('Connection', conn)

        queue.put(resp)

    def serve_error(self, code, request, queue=None):
        return self.serve(config('error%d' % code, default=None),
                code=code, request=request, queue=queue)

    def make_date_string(self):
        """Make string of date and time

        Returns:
            str: formatted string of date and time
        """
        return time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime())