#!/usr/bin/python

import sys, os, getopt, threading, logging, time
import ashd.proto, ashd.util, ashd.perf, ashd.serve
try:
    import pdm.srv
except:
    pdm = None

def usage(out):
    out.write("usage: ashd-wsgi [-hAL] [-m PDM-SPEC] [-p MODPATH] [-l REQLIMIT] HANDLER-MODULE [ARGS...]\n")

reqlimit = 0
modwsgi_compat = False
setlog = True
opts, args = getopt.getopt(sys.argv[1:], "+hAp:l:m:")
for o, a in opts:
    if o == "-h":
        usage(sys.stdout)
        sys.exit(0)
    elif o == "-p":
        sys.path.insert(0, a)
    elif o == "-L":
        setlog = False
    elif o == "-A":
        modwsgi_compat = True
    elif o == "-l":
        reqlimit = int(a)
    elif o == "-m":
        if pdm is not None:
            pdm.srv.listen(a)
if len(args) < 1:
    usage(sys.stderr)
    sys.exit(1)
if setlog:
    logging.basicConfig(format="ashd-wsgi(%(name)s): %(levelname)s: %(message)s")
log = logging.getLogger("ashd-wsgi")

try:
    handlermod = __import__(args[0], fromlist = ["dummy"])
except ImportError, exc:
    sys.stderr.write("ashd-wsgi: handler %s not found: %s\n" % (args[0], exc.message))
    sys.exit(1)
if not modwsgi_compat:
    if not hasattr(handlermod, "wmain"):
        sys.stderr.write("ashd-wsgi: handler %s has no `wmain' function\n" % args[0])
        sys.exit(1)
    handler = handlermod.wmain(*args[1:])
else:
    if not hasattr(handlermod, "application"):
        sys.stderr.write("ashd-wsgi: handler %s has no `application' object\n" % args[0])
        sys.exit(1)
    handler = handlermod.application

cwd = os.getcwd()
def absolutify(path):
    if path[0] != '/':
        return os.path.join(cwd, path)
    return path

def unquoteurl(url):
    buf = ""
    i = 0
    while i < len(url):
        c = url[i]
        i += 1
        if c == '%':
            if len(url) >= i + 2:
                c = 0
                if '0' <= url[i] <= '9':
                    c |= (ord(url[i]) - ord('0')) << 4
                elif 'a' <= url[i] <= 'f':
                    c |= (ord(url[i]) - ord('a') + 10) << 4
                elif 'A' <= url[i] <= 'F':
                    c |= (ord(url[i]) - ord('A') + 10) << 4
                else:
                    raise ValueError("Illegal URL escape character")
                if '0' <= url[i + 1] <= '9':
                    c |= ord(url[i + 1]) - ord('0')
                elif 'a' <= url[i + 1] <= 'f':
                    c |= ord(url[i + 1]) - ord('a') + 10
                elif 'A' <= url[i + 1] <= 'F':
                    c |= ord(url[i + 1]) - ord('A') + 10
                else:
                    raise ValueError("Illegal URL escape character")
                buf += chr(c)
                i += 2
            else:
                raise ValueError("Incomplete URL escape character")
        else:
            buf += c
    return buf

def mkenv(req):
    env = {}
    env["wsgi.version"] = 1, 0
    for key, val in req.headers:
        env["HTTP_" + key.upper().replace("-", "_")] = val
    env["SERVER_SOFTWARE"] = "ashd-wsgi/1"
    env["GATEWAY_INTERFACE"] = "CGI/1.1"
    env["SERVER_PROTOCOL"] = req.ver
    env["REQUEST_METHOD"] = req.method
    env["REQUEST_URI"] = req.url
    name = req.url
    p = name.find('?')
    if p >= 0:
        env["QUERY_STRING"] = name[p + 1:]
        name = name[:p]
    else:
        env["QUERY_STRING"] = ""
    if name[-len(req.rest):] == req.rest:
        # This is the same hack used in call*cgi.
        name = name[:-len(req.rest)]
    try:
        pi = unquoteurl(req.rest)
    except:
        pi = req.rest
    if name == '/':
        # This seems to be normal CGI behavior, but see callcgi.c for
        # details.
        pi = "/" + pi
        name = ""
    env["SCRIPT_NAME"] = name
    env["PATH_INFO"] = pi
    if "Host" in req: env["SERVER_NAME"] = req["Host"]
    if "X-Ash-Server-Address" in req: env["SERVER_ADDR"] = req["X-Ash-Server-Address"]
    if "X-Ash-Server-Port" in req: env["SERVER_PORT"] = req["X-Ash-Server-Port"]
    if "X-Ash-Protocol" in req and req["X-Ash-Protocol"] == "https": env["HTTPS"] = "on"
    if "X-Ash-Address" in req: env["REMOTE_ADDR"] = req["X-Ash-Address"]
    if "X-Ash-Port" in req: env["REMOTE_PORT"] = req["X-Ash-Port"]
    if "Content-Type" in req:
        env["CONTENT_TYPE"] = req["Content-Type"]
        # The CGI specification does not strictly require this, but
        # many actualy programs and libraries seem to.
        del env["HTTP_CONTENT_TYPE"]
    if "Content-Length" in req:
        env["CONTENT_LENGTH"] = req["Content-Length"]
        del env["HTTP_CONTENT_LENGTH"]
    if "X-Ash-File" in req: env["SCRIPT_FILENAME"] = absolutify(req["X-Ash-File"])
    if "X-Ash-Protocol" in req: env["wsgi.url_scheme"] = req["X-Ash-Protocol"]
    env["wsgi.input"] = req.sk
    env["wsgi.errors"] = sys.stderr
    env["wsgi.multithread"] = True
    env["wsgi.multiprocess"] = False
    env["wsgi.run_once"] = False
    return env

if reqlimit != 0:
    guard = ashd.serve.abortlimiter(reqlimit).call
else:
    guard = lambda fun: fun()

class reqthread(ashd.serve.wsgithread):
    def __init__(self, req):
        super(reqthread, self).__init__()
        self.req = req.dup()
    
    def handlewsgi(self):
        return handler(self.env, self.startreq)

    def writehead(self, status, headers):
        try:
            self.req.sk.write("HTTP/1.1 %s\n" % status)
            for nm, val in headers:
                self.req.sk.write("%s: %s\n" % (nm, val))
            self.req.sk.write("\n")
        except IOError:
            raise ashd.serve.closed()

    def writedata(self, data):
        try:
            self.req.sk.write(data)
            self.req.sk.flush()
        except IOError:
            raise ashd.serve.closed()

    def handle(self):
        self.env = mkenv(self.req)
        reqevent = ashd.perf.request(self.env)
        exc = (None, None, None)
        try:
            super(reqthread, self).handle()
            if self.status:
                reqevent.response([self.status, self.headers])
        except:
            exc = sys.exc_info()
            raise
        finally:
            reqevent.__exit__(*exc)

    def run(self):
        try:
            guard(super(reqthread, self).run)
        finally:
            self.req.close()
    
def handle(req):
    reqthread(req).start()

ashd.util.serveloop(handle)
