import py
import autopath
import urllib2
import threading
import time
import sys
import traceback
import socket
from StringIO import StringIO

from httpmp2.httpserver import *
from httpmp2 import log
log.MINLEVEL = log.ERROR

class config:
    port = 8084 # start port, if this doesn't work others get tried > <port>
    host = ''

class TestHTTPServer(object):
    def setup_class(cls):
        def root(config, request, channel):
            for line in ('HTTP/1.0 200 Ok',
                         'Content-Type: text/plain',
                         'Content-Length: 4',
                         '',
                         'test'):
                yield line + '\r\n'

        def buggyhandler(config, request, channel):
            foobar()

        def feeder(config, request, channel):
            for line in ('HTTP/1.0 200 Ok\r\n',
                         'Connection: close\r\n\r\n'):
                yield line
            buffer = []
            def feed():
                for i in xrange(10):
                    channel.push('%s\r\n' % (i,))
                    time.sleep(0.1)
                channel.server.poll_next()
                channel.cleanup()
            t = threading.Thread(target=feed)
            t.start()
            yield None

        cls.server = HTTPServer(config, {'/': {'GET': root},
                                         '/buggy': {'GET': buggyhandler},
                                         '/feed': {'GET': feeder},
                                         })
        for i in range(10):
            try:
                cls.server.initialize()
            except socket.error, e:
                config.port = config.port + 1
            else:
                break
        else:
            py.test.skip('failed to initialize, error %s' % (e,))

    def geturl(self, path, ret, data):
        try:
            res = urllib2.urlopen('http://localhost:%s%s' % (
                                   config.port, path), data)
            res.body = res.read()
            error = None
        except:
            exc, e, tb = sys.exc_info()
            error = e
            res = None
            del tb
        ret.append((res, error))

    def load(self, path, data=None):
        ret = []
        t = threading.Thread(target=self.geturl, args=(path, ret, data))
        t.start()
        for i in range(50):
            if not t.isAlive():
                break
            self.server.serve(count=1, timeout=0.1, raise_exceptions=True)
            time.sleep(0.1)
        else:
            raise Exception('more than %s loops required to finish request' % (
                            i + 1,))
        return ret[0]

    def test_200(self):
        res, error = self.load('/')
        assert res and res.code == 200, error.code

    def test_404(self):
        res, error = self.load('/foo')
        assert error.code == 404, error.code

    def test_405(self):
        res, error = self.load('/', 'foo')
        assert error.code == 405, error.code

    def test_500(self):
        res, error = self.load('/buggy')
        print error
        assert error.code == 500, error.code

    def test_feeder(self):
        res, error = self.load('/feed')
        assert res and res.code == 200, error.code
        expected = ''.join(['%s\r\n' % (x,) for x in range(10)])
        assert res.body == expected, res.body

def test_NotFoundHandler():
    req = Request()
    req.status = 404
    req.method = 'GET'
    req.path = '/foo'
    req.httpver = 'HTTP/1.0'
    h = NotFoundHandler(None, req, None)
    response = ''.join(list(h))
    assert response == '\r\n'.join([
        'HTTP/1.0 404 Not Found',
        'Content-Type: text/plain',
        'Content-Length: 21',
        '',
        'Path /foo not found',
        '',
    ])

def test_MethodNotSupportedHandler():
    req = Request()
    req.status = 404
    req.path = '/foo'
    req.method = 'GET'
    req.httpver = 'HTTP/1.0'
    h = MethodNotSupportedHandler(None, req, None)
    response = ''.join(list(h))
    assert response == '\r\n'.join([
        'HTTP/1.0 405 Method Not Supported',
        'Content-Type: text/plain',
        'Content-Length: 26',
        '',
        'Method GET not supported',
        '',
    ])

def test_FileHandler():
    req = Request()
    req.status = 404
    req.method = 'GET'
    req.path = '/foo'
    req.httpver = 'HTTP/1.0'
    tempdir = py.test.ensuretemp('TestFileHandler')
    foo = tempdir.join('foo.txt').ensure(file=True)
    foo.write('foo\n' * 10)
    h = FileHandler(foo.strpath, 'text/plain', None, req, None)
    h.bufsize = 8
    response = ''.join(list(h))
    assert response.startswith('HTTP/1.0 200 Ok\r\n')
    assert 'Content-Length: 40\r\n' in response
    assert 'Content-Type: text/plain\r\n'
    body = response.split('\r\n\r\n')[1]
    assert body == 'foo\n' * 10

def test_DirectoryHandler():
    req = Request()
    req.status = 404
    req.method = 'GET'
    req.path = '/somedir/foo/bar.txt'
    req.httpver = 'HTTP/1.0'
    tempdir = py.test.ensuretemp('TestFileHandler')
    tempdir.join('foo').ensure(dir=True)
    tempdir.join('foo/bar.txt').ensure(file=True).write('bar')
    h = DirectoryHandler(tempdir.strpath, '/somedir', None, req, None)
    response = ''.join(list(h))
    assert response.startswith('HTTP/1.0 200 Ok\r\n')
    assert 'Content-Type: text/plain\r\n' in response
    assert 'Content-Length: 3\r\n' in response
    assert response.endswith('\r\n\r\nbar')

class TestHTTPChannel(object):
    def test_collect_incoming_data(self):
        h = HTTPChannel(config, None, 1, FakeServer())
        assert h.buffer == []
        h.collect_incoming_data('foo')
        assert h.buffer == ['foo']
        h.collect_incoming_data('bar')
        assert h.buffer == ['foo', 'bar']

    def test_found_terminator(self):
        # do a simple HTTP request here...
        h = HTTPChannel(config, None, 1, FakeServer())
        h.collect_incoming_data('GET / HTTP/1.0')
        assert h.request.method is None
        h.found_terminator()
        assert h.request.method == 'GET'
        assert h.request.path == '/'
        assert h.request.httpver == 'HTTP/1.0'
        assert h.request.headers is None
        h.collect_incoming_data('Connection: close')
        h.found_terminator()
        h.collect_incoming_data('Content-Length: 10')
        h.found_terminator()
        assert h.request.headers is None
        h.found_terminator()
        assert h.request.headers is not None
        assert h.request.headers['connection'] == 'close'
        # XXX test body

class TestRequest(object):
    def test_parse_request_line(self):
        r = Request()
        l = 'GET / HTTP/1.0'
        reqinfo = r.parse_request_line(l)
        assert reqinfo == ['GET', '/', 'HTTP/1.0']
        
        r = Request()
        l = 'POST /foo/bar?foo=bar#foobar HTTP/1.1'
        reqinfo = r.parse_request_line(l)
        assert reqinfo == ['POST', '/foo/bar?foo=bar#foobar', 'HTTP/1.1']

        r = Request()
        l = 'GET /'
        reqinfo = r.parse_request_line(l)
        assert reqinfo is None

        r = Request()
        l = 'GET /%s HTTP/1.0' % ('x' * 8192,)
        reqinfo = r.parse_request_line(l)
        assert reqinfo is None

    def test_get_form(self):
        r = Request()
        r.headers = {}
        r.method = 'GET'
        r.path = '/foo?bar=baz'
        form = r.form
        assert form.getvalue('bar') == 'baz'

        r = Request()
        r.method = 'POST'
        r.path = '/foo'
        r.body = 'qux=quux'
        r.headers = {
            'content-length': '8',
            'content-type': 'application/x-www-form-urlencoded',
        }
        form = r.form
        assert form.getvalue('qux') == 'quux'

    def test_parse_headers(self):
        r = Request()
        headers = r.parse_headers('')
        assert len(headers) == 0

        h = 'Content-Type: text/plain\nConnection: close\n'
        headers = r.parse_headers(h)
        assert len(headers) == 2
        assert headers['content-type'] == 'text/plain'
        assert headers['connection'] == 'close'

        h = 'Foobar\nBaz\n'
        headers = r.parse_headers(h)
        assert len(headers) == 0

    def test_keepalive(self):
        r = Request()
        r.httpver = 'HTTP/1.0'
        r.headers = {}
        assert not r.keepalive()
        r.headers['connection'] = 'Keep-Alive'
        assert r.keepalive()
        r.headers = {}
        r.httpver = 'HTTP/1.1'
        assert r.keepalive()
        r.headers['connection'] = 'Close'
        assert not r.keepalive()

# helper stuff
class FakeServer(object):
    def __init__(self, handlers=None):
        if handlers == None:
            handlers = {}
        self.handlers = handlers


