'''
Created on 2013-4-21

@author: Xsank
'''

import os
import time
import itertools
import functools
import mimetypes
from json import dumps
from urlparse import urljoin
from traceback import format_exc
from types import StringType

from config import HTTP_CODES,DEBUG
from http import Request,Response
from exception import HTTPError,HTTPResponse
from router import Router
from util import depr,yieldroutes,toa,parse_date,_reloader_child,_reloader_observer
from util import WSGIFileWrapper
from server import ServerAdapter,WSGIRefServer


request = Request()
response = Response()


class Brick(object):

    def __init__(self, catchall=True, autojson=True, config=None):
        self.routes = Router()
        self._logger=None
        self.mounts = {}
        self.error_handler = {}
        self.catchall = catchall
        self.config = config or {}
        self.serve = True
        self.castfilter = []
        if autojson and dumps:
            self.add_filter(dict, dumps)

    def optimize(self, *a, **ka):
        depr("Brick.optimize() is obsolete.")

    def mount(self, app, script_path):
        if not isinstance(app, Brick):
            raise TypeError('Only Brick instances are supported for now.')
        script_path = '/'.join(filter(None, script_path.split('/')))
        path_depth = script_path.count('/') + 1
        if not script_path:
            raise TypeError('Empty script_path. Perhaps you want a merge()?')
        for other in self.mounts:
            if other.startswith(script_path):
                raise TypeError('Conflict with existing mount: %s' % other)
        @self.route('/%s/:#.*#' % script_path, method="ANY")
        def mountpoint():
            request.path_shift(path_depth)
            return app.handle(request.path, request.method)
        self.mounts[script_path] = app

    def add_filter(self, ftype, func):
        if not isinstance(ftype, type):
            raise TypeError("Expected type object, got %s" % type(ftype))
        self.castfilter = [(t, f) for (t, f) in self.castfilter if t != ftype]
        self.castfilter.append((ftype, func))
        self.castfilter.sort()

    def match_url(self, path, method='GET'):
        path, method = path.strip().lstrip('/'), method.upper()
        callbacks, args = self.routes.match(path)
        if not callbacks:
            raise HTTPError(404, "Not found: " + path)
        if method in callbacks:
            return callbacks[method], args
        if method == 'HEAD' and 'GET' in callbacks:
            return callbacks['GET'], args
        if 'ANY' in callbacks:
            return callbacks['ANY'], args
        allow = [m for m in callbacks if m != 'ANY']
        if 'GET' in allow and 'HEAD' not in allow:
            allow.append('HEAD')
        raise HTTPError(405, "Method not allowed.",
                        header=[('Allow',",".join(allow))])

    def get_url(self, routename, **kargs):
        scriptname = request.environ.get('SCRIPT_NAME', '').strip('/') + '/'
        location = self.routes.build(routename, **kargs).lstrip('/')
        return urljoin(urljoin('/', scriptname), location)

    def route(self, path=None, method='GET', **kargs):
        def wrapper(callback):
            routes = [path] if path else yieldroutes(callback)
            methods = method.split(';') if isinstance(method, str) else method
            for r in routes:
                for m in methods:
                    r, m = r.strip().lstrip('/'), m.strip().upper()
                    old = self.routes.get_route(r, **kargs)
                    if old:
                        old.target[m] = callback
                    else:
                        self.routes.add(r, {m: callback}, **kargs)
                        self.routes.compile()
            return callback
        return wrapper

    def get(self, path=None, method='GET', **kargs):
        return self.route(path, method, **kargs)

    def post(self, path=None, method='POST', **kargs):
        return self.route(path, method, **kargs)

    def put(self, path=None, method='PUT', **kargs):
        return self.route(path, method, **kargs)

    def delete(self, path=None, method='DELETE', **kargs):
        return self.route(path, method, **kargs)

    def error(self, code=500):
        def wrapper(handler):
            self.error_handler[int(code)] = handler
            return handler
        return wrapper

    def handle(self, url, method):
        if not self.serve:
            return HTTPError(503, "Server stopped")
        try:
            handler, args = self.match_url(url, method)
            return handler(**args)
        except HTTPResponse, e:
            return e
        except Exception, e:
            if isinstance(e, (KeyboardInterrupt, SystemExit, MemoryError))\
            or not self.catchall:
                raise
            print e
            return HTTPError(500, 'Unhandled exception', e, format_exc(10))

    def _cast(self, out, request, response, peek=None):
        for testtype, filterfunc in self.castfilter:
            if isinstance(out, testtype):
                return self._cast(filterfunc(out), request, response)

        if not out:
            response.headers['Content-Length'] = 0
            return []
        if isinstance(out, (tuple, list))\
        and isinstance(out[0], (StringType, unicode)):
            out = out[0][0:0].join(out) 
        if isinstance(out, unicode):
            out = out.encode(response.charset)
        if isinstance(out, StringType):
            response.headers['Content-Length'] = str(len(out))
            return [out]
        if isinstance(out, HTTPError):
            out.apply(response)
            return self._cast(self.error_handler.get(out.status, repr)(out), request, response)
        if isinstance(out, HTTPResponse):
            out.apply(response)
            return self._cast(out.output, request, response)

        if hasattr(out, 'read'):
            if 'wsgi.file_wrapper' in request.environ:
                return request.environ['wsgi.file_wrapper'](out)
            elif hasattr(out, 'close') or not hasattr(out, '__iter__'):
                return WSGIFileWrapper(out)

        try:
            out = iter(out)
            first = out.next()
            while not first:
                first = out.next()