from __future__ import generators

from urllib import quote, string

import UserDict, math, time
from cStringIO import StringIO

from hack.web2 import http_headers, iweb, stream, responsecode
from twisted.internet import defer, address
from twisted.python import components
from twisted.spread import pb

from zope.interface import implements

class HeaderAdapter(UserDict.DictMixin):
    def __init__(self, headers):
        self._headers = headers
        
    def __getitem__(self, name):
        raw = self._headers.getRawHeaders(name)
        if raw is None:
            raise KeyError(name)
        return ', '.join(raw)

    def __setitem__(self, name, value):
        self._headers.setRawHeaders([value])
        
    def __delitem__(self, name):
        if not self._headers.hasHeader(name):
            raise KeyError(name)
        self._headers.removeHeader(name)

    def iteritems(self):
        for k,v in self._headers.getAllRawHeaders():
            yield k, ', '.join(v)

    def keys(self):
        return [k for k, _ in self.iteritems()]

    def __iter__(self):
        for k, _ in self.iteritems():
            yield k

    def has_key(self, name):
        return self._headers.hasHeader(name)

def makeOldRequestAdapter(original):
    # Cache the adapter. Replace this with a more better generalized
    # mechanism when one becomes available.
    if not hasattr(original, '_oldRequest'):
        original._oldRequest = OldRequestAdapter(original)
    return original._oldRequest

def _addressToTuple(addr):
    if isinstance(addr, address.IPv4Address):
        return ('INET', addr.host, addr.port)
    elif isinstance(addr, address.UNIXAddress):
        return ('UNIX', addr.name)
    else:
        return tuple(addr)

class OldRequestAdapter(pb.Copyable, components.Componentized, object):
    """Adapt old requests to new request
    """
    implements(iweb.IOldRequest)
    
    def _getFrom(where, name):
        def _get(self):
            return getattr(getattr(self, where), name)
        return property(_get)

    def _getsetFrom(where, name):
        def _get(self):
            return getattr(getattr(self, where), name)
        def _set(self, new):
            setattr(getattr(self, where), name, new)
        def _del(self):
            delattr(getattr(self, where), name)
        return property(_get, _set, _del)

    def _getsetHeaders(where):
        def _get(self):
            headers = getattr(self, where).headers
            return HeaderAdapter(headers)

        def _set(self, newheaders):
            headers = http_headers.Headers()
            for n,v in newheaders.items():
                headers.setRawHeaders(n, (v,))
            newheaders = headers
            getattr(self, where).headers = newheaders
            
        return property(_get, _set)
    
    
    code = _getsetFrom('response', 'code')
    code_message = ""
    
    method = _getsetFrom('request', 'method')
    uri = _getsetFrom('request', 'uri')
    def _getClientproto(self):
        return "HTTP/%d.%d" % self.request.clientproto
    clientproto = property(_getClientproto)
    
    received_headers = _getsetHeaders('request')
    headers = _getsetHeaders('response')
    path = _getsetFrom('request', 'path')
    
    # cookies = # Do I need this?
    # received_cookies = # Do I need this?
    content = StringIO() #### FIXME
    args = _getsetFrom('request', 'args')
    # stack = # WTF is stack?
    prepath = _getsetFrom('request', 'prepath')
    postpath = _getsetFrom('request', 'postpath')

    def _getClient(self):
        return "WTF"
    client = property(_getClient)
    
    def _getHost(self):
        return address.IPv4Address("TCP", self.request.host, self.request.port)
    host = property(_getHost)
    
    def __init__(self, request):
        from hack.web2 import http
        components.Componentized.__init__(self)
        self.request = request
        self.response = http.Response(stream=stream.ProducerStream())
        # This deferred will be fired by the first call to write on OldRequestAdapter
        # and will cause the headers to be output.
        self.deferredResponse = defer.Deferred()

    def getStateToCopyFor(self, issuer):
        # This is for distrib compatibility
        x = {}

        x['prepath'] = self.prepath
        x['postpath'] = self.postpath
        x['method'] = self.method
        x['uri'] = self.uri

        x['clientproto'] = self.clientproto
        self.content.seek(0, 0)
        x['content_data'] = self.content.read()
        x['remote'] = pb.ViewPoint(issuer, self)

        x['host'] = _addressToTuple(self.request.chanRequest.channel.transport.getHost())
        x['client'] = _addressToTuple(self.request.chanRequest.channel.transport.getPeer())

        return x

    def getTypeToCopy(self):
        # lie to PB so the ResourcePublisher doesn't have to know web2 exists
        # which is good because web2 doesn't exist.
        return 'twisted.web.server.Request'

    def registerProducer(self, producer, streaming):
        self.response.stream.registerProducer(producer, streaming)
        
    def unregisterProducer(self):
        self.response.stream.unregisterProducer()
        
    def finish(self):
        if self.deferredResponse is not None:
            d = self.deferredResponse
            self.deferredResponse = None
            d.callback(self.response)
        self.response.stream.finish()
        
    def write(self, data):
        if self.deferredResponse is not None:
            d = self.deferredResponse
            self.deferredResponse = None
            d.callback(self.response)
        self.response.stream.write(data)
        
    def getHeader(self, name):
        raw = self.request.headers.getRawHeaders(name)
        if raw is None:
            return None
        return ', '.join(raw)

    def setHeader(self, name, value):
        """Set an outgoing HTTP header.
        """
        self.response.headers.setRawHeaders(name, [value])
        
    def setResponseCode(self, code, message=None):
        # message ignored
        self.response.code = code

    def setLastModified(self, when):
        # Never returns CACHED -- can it and still be compliant?
        when = long(math.ceil(when))
        self.response.headers.setHeader('last-modified', when)
        return None

    def setETag(self, etag):
        self.response.headers.setRawHeaders('etag', [etag])
        return None

    def getAllHeaders(self):
        return dict(self.headers.iteritems())

    def getRequestHostname(self):
        return self.request.host


    def getCookie(self, key):
        for cookie in self.request.headers.getHeader('cookie', ()):
            if cookie.name == key:
                return cookie.value
            
        return None

    def addCookie(self, k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None):
        if expires is None and max_age is not None:
            expires=max_age-time.time()
        cookie = http_headers.Cookie(k,v, expires=expires, domain=domain, path=path, comment=comment, secure=secure)
        self.response.headers.setHeader('set-cookie', self.request.headers.getHeader('set-cookie', ())+(cookie,))

    def notifyFinish(self):
        ### FIXME
        return None
#        return self.request.notifyFinish()
    
    def getHost(self):
        return self.host
    
    def setHost(self, host, port, ssl=0):
        self.request.host = host
        self.request.port = port
        self.request.scheme = ssl and 'https' or 'http'

    def isSecure(self):
        return self.request.scheme == 'https'
    
    def getClientIP(self):
        if isinstance(self.request.chanRequest.getRemoteHost(), address.IPv4Address):
            return self.client.host
        else:
            return None
        return self.request.chanRequest.getRemoteHost()
        return "127.0.0.1"

    def getClient(self):
        return "127.0.0.1"

### FIXME:
    def getUser(self):
        return ""

    def getPassword(self):
        return ""

# Identical to original methods -- hopefully these don't have to change
    def sibLink(self, name):
        "Return the text that links to a sibling of the requested resource."
        if self.postpath:
            return (len(self.postpath)*"../") + name
        else:
            return name

    def childLink(self, name):
        "Return the text that links to a child of the requested resource."
        lpp = len(self.postpath)
        if lpp > 1:
            return ((lpp-1)*"../") + name
        elif lpp == 1:
            return name
        else: # lpp == 0
            if len(self.prepath) and self.prepath[-1]:
                return self.prepath[-1] + '/' + name
            else:
                return name

    def redirect(self, url):
        """Utility function that does a redirect.
        
        The request should have finish() called after this.
        """
        self.setResponseCode(responsecode.FOUND)
        self.setHeader("location", url)
    
    def prePathURL(self):
        port = self.getHost().port
        if self.isSecure():
            default = 443
        else:
            default = 80
        if port == default:
            hostport = ''
        else:
            hostport = ':%d' % port
        return quote('http%s://%s%s/%s' % (
            self.isSecure() and 's' or '',
            self.getRequestHostname(),
            hostport,
            string.join(self.prepath, '/')), "/:")

#     def URLPath(self):
#         from twisted.python import urlpath
#         return urlpath.URLPath.fromRequest(self)

# But nevow wants it to look like this... :(
    def URLPath(self):
        from nevow import url
        return url.URL.fromContext(self)

    def rememberRootURL(self, url=None):
        """
        Remember the currently-processed part of the URL for later
        recalling.
        """
        if url is None:
            url = self.prePathURL()
            # remove one segment
            self.appRootURL = url[:url.rindex("/")]
        else:
            self.appRootURL = url

    def getRootURL(self):
        """
        Get a previously-remembered URL.
        """
        return self.appRootURL

    
    session = None

    def getSession(self, sessionInterface = None):
        # Session management
        if not self.session:
            # FIXME: make sitepath be something
            cookiename = string.join(['TWISTED_SESSION'] + self.sitepath, "_")
            sessionCookie = self.getCookie(cookiename)
            if sessionCookie:
                try:
                    self.session = self.site.getSession(sessionCookie)
                except KeyError:
                    pass
            # if it still hasn't been set, fix it up.
            if not self.session:
                self.session = self.site.makeSession()
                self.addCookie(cookiename, self.session.uid, path='/')
        self.session.touch()
        if sessionInterface:
            return self.session.getComponent(sessionInterface)
        return self.session


class OldNevowResourceAdapter(object):
    implements(iweb.IResource)
    
    def __init__(self, original):
        # Can't use self.__original= because of __setattr__.
        self.__dict__['_OldNevowResourceAdapter__original']=original
        
    def __getattr__(self, name):
        return getattr(self.__original, name)

    def __setattr__(self, name, value):
        setattr(self.__original, name, value)

    def __delattr__(self, name):
        delattr(self.__original, name)

    def locateChild(self, ctx, segments):
        from hack.web2.server import parsePOSTData
        request = iweb.IRequest(ctx)
        if request.method == "POST":
            return parsePOSTData(request).addCallback(
                lambda x: self.__original.locateChild(ctx, segments))
        return self.__original.locateChild(ctx, segments)
    
    def renderHTTP(self, ctx):
        from hack.web2.server import parsePOSTData
        request = iweb.IRequest(ctx)
        if request.method == "POST":
            return parsePOSTData(request).addCallback(self.__reallyRender, ctx)
        return self.__reallyRender(None, ctx)

    def __reallyRender(self, ignored, ctx):
        # This deferred will be called when our resource is _finished_
        # writing, and will make sure we write the rest of our data
        # and finish the connection.
        defer.maybeDeferred(self.__original.renderHTTP, ctx).addCallback(self.__finish, ctx)

        # Sometimes the __original.renderHTTP will write() before we
        # even get this far, and we don't want to return
        # oldRequest.deferred if it's already been set to None.
        oldRequest = iweb.IOldRequest(ctx)
        if oldRequest.deferredResponse is None:
            return oldRequest.response
        return oldRequest.deferredResponse

    def __finish(self, data, ctx):
        oldRequest = iweb.IOldRequest(ctx)
        oldRequest.write(data)
        oldRequest.finish()


class OldResourceAdapter(object):
    implements(iweb.IOldNevowResource)

    def __init__(self, original):
        self.original = original

    def __repr__(self):
        return "<%s @ 0x%x adapting %r>" % (self.__class__.__name__, id(self), self.original)

    def locateChild(self, req, segments):
        import server
        request = iweb.IOldRequest(req)
        if self.original.isLeaf:
            return self, server.StopTraversal
        name = segments[0]
        if name == '':
            res = self
        else:
            request.prepath.append(request.postpath.pop(0))
            res = self.original.getChildWithDefault(name, request)
            request.postpath.insert(0, request.prepath.pop())
            
            if isinstance(res, defer.Deferred):
                return res.addCallback(lambda res: (res, segments[1:]))
            
        return res, segments[1:]

    def _handle_NOT_DONE_YET(self, data, request):
        from twisted.web.server import NOT_DONE_YET
        if data == NOT_DONE_YET:
            # Return a deferred that will never fire, so the finish
            # callback doesn't happen. This is because, when returning
            # NOT_DONE_YET, the page is responsible for calling finish.
            return defer.Deferred()
        else:
            return data

    def renderHTTP(self, req):
        request = iweb.IOldRequest(req)
        result = defer.maybeDeferred(self.original.render, request).addCallback(
            self._handle_NOT_DONE_YET, request)
        return result

__all__ = []
