import httplib, time
from cStringIO import StringIO

import pycurl

from disco.events import Message
from disco.error import CommError

class CurlResponse(object):
    def __init__(self, connection):
        self.connection = connection
        self.headers = {}
        self.buffer = StringIO()

    def getheader(self, header, default=None):
        return self.headers.get(header.lower(), default)

    def getheaders(self):
        return self.headers.items()

    def header_function(self, header):
        if ':' in header:
            k, v = header.split(':', 1)
            self.headers[k.lower().strip()] = v.strip()

    def read(self):
        return self.buffer.getvalue()

    @property
    def status(self):
        return self.connection['HTTP_CODE']

class HTTPConnection(object):
    defaults = {'CONNECTTIMEOUT': 20,
                'FRESH_CONNECT': 1,
                'LOW_SPEED_LIMIT': 1024, # 1kbps
                'LOW_SPEED_TIME': 2 * 60,
                'NOSIGNAL': 1}

    def __init__(self, netloc):
        self.handle = pycurl.Curl()
        for k, v in self.defaults.items():
            self[k] = v

        self.netloc = netloc
        self.response = CurlResponse(self)
        self['HEADERFUNCTION'] = self.response.header_function
        self['WRITEFUNCTION']  = self.response.buffer.write

    def __getitem__(self, key):
        return self.handle.getinfo(getattr(pycurl, key))

    def __setitem__(self, key, value):
        self.handle.setopt(getattr(pycurl, key), value)

    def getresponse(self):
        return self.response

    def prepare(self, method, url, body=None, headers={}):
        self['URL'] = '%s%s' % (self.netloc, url)

        if method == 'DELETE':
            self['CUSTOMREQUEST'] = method
        elif method == 'PUT':
            self['UPLOAD'] = 1
        elif method == 'POST':
            self['POST'] = 1

        if body is not None:
            size = len(body) if hasattr(body, '__len__') else None
            read = body.read if hasattr(body, 'read') else StringIO(body).read

            if method == 'PUT':
                if size:
                    self['INFILESIZE'] = size
            elif method == 'POST':
                self['POSTFIELDSIZE'] = size if size is not None else -1
            self['READFUNCTION'] = read

        self['HTTPHEADER'] = ['%s:%s' % item for item in headers.items()] +\
                             ['Expect:'] # work-around for lighttpd
        return self

    def request(self, method, url, body=None, headers={}):
        self.prepare(method, url, body=body, headers=headers)
        try:
            self.handle.perform()
        except pycurl.error, e:
            raise httplib.HTTPException(self.handle.errstr())

class MultiPut(object):
    def __init__(self, urls, source):
        from disco.util import urlresolve
        self.multi = pycurl.CurlMulti()
        self.pending = [(url, HTTPConnection('').prepare('PUT',
                                                         urlresolve(url),
                                                         body=source))
                        for url in urls]
        for url, conn in self.pending:
            self.multi.add_handle(conn.handle)

    def perform(self):
        num_handles = True
        while num_handles:
            ret = self.multi.select(1.0)
            if ret == -1:
                continue
            while True:
                ret, num_handles = self.multi.perform()
                if ret != pycurl.E_CALL_MULTI_PERFORM:
                    break
        for url, conn in self.pending:
            yield url, conn.getresponse()

def upload(urls, source, retries=10):
    unavailable = []

    for url, response in MultiPut(urls, source).perform():
        status = response.status
        if str(status).startswith('2'):
            yield response.read()
        elif status == httplib.SERVICE_UNAVAILABLE:
            if not retries:
                raise CommError("Maximum number of retries reached", url)
            unavailable.append(url)
        else:
            raise CommError("Upload failed: %s" % response.read(), url, status)

    if unavailable:
        for response in upload(unavailable, source, retries=retries-1):
            yield response

@@ -182,59 +182,10 @@
 self
-, connection):%0A        self.connection = connection
+):
 %0A   
@@ -634,89 +634,8 @@
 ()%0A%0A
-    @property%0A    def status(self):%0A        return self.connection%5B'HTTP_CODE'%5D%0A%0A
 clas
@@ -1059,20 +1059,16 @@
 esponse(
-self
 )%0A      
@@ -1367,24 +1367,24 @@
 y), value)%0A%0A
-
     def getr
@@ -1390,32 +1390,81 @@
 response(self):%0A
+        self.response.status = self%5B'HTTP_CODE'%5D%0A
         return s

remove circular reference from comm_pycurl