import asyncio, os, inspect, logging, functools

from urllib import parse

from aiohttp import web

from apis import APIError

def get(path):
    # 函数通过@get()的装饰就附带了URL信息。
    '''
    Define decorator @get('/path')
    '''
    # wrapper装饰器：
    # 装饰器本质上是一个Python函数，它可以让其他函数在不需要做任何代码变动的前提下增加额外功能，
    # 装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景，比如：插入日志、性能测试、事务处理、缓存、权限校验等场景。
    # 装饰器是解决这类问题的绝佳设计，有了装饰器，我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
    # 可变参数*args和关键字参数**kwargs，有了这两个参数，装饰器就可以用于任意目标函数
    def decorator(func):
        # 返回了一个partial对象，这个对象对update_wrapper进行了包装，固定了wrapped，assigned，updated三个参数。
        # wraps本省就是一个装饰器，因为它返回的是一个“函数”即partial对象，这个对象接收函数作为参数，同时以函数作为返回值。
        # 把wrapped函数的属性拷贝到wrapper函数中。
        # wrapped是被装饰的原函数
        # wrapper是被装饰器装饰后的新函数。
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = 'GET'
        wrapper.__route__ = path
        return wrapper
    return decorator

def post(path):

    '''
    Define decorator @post('/path')
    '''
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            return func(*args, **kw)
        wrapper.__method__ = 'POST'
        wrapper.__route__ = path
        return wrapper
    return decorator


def get_required_kw_args(fn):
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY and param.default == inspect.Parameter.empty:
            # 在 args 里加上仅包含关键字（keyword）的参数， 且不包括默认值， 然后返回 args
            args.append(name)
    return tuple(args)
    # 所以这个函数的作用和名称一样， 得到需要的关键字参数, 下面同理

def get_named_kw_args(fn):
    args = []
    params = inspect.signature(fn).parameters
    for name, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            args.append(name)
    return tuple(args)

def has_named_kw_args(fn):
    params = inspect.signature(fn).parameters
    for _, param in params.items():
        if param.kind == inspect.Parameter.KEYWORD_ONLY:
            return True

def has_var_kw_arg(fn):
    params = inspect.signature(fn).parameters
    for _, param in params.items():
        if param.kind == inspect.Parameter.VAR_KEYWORD:
            return True

def has_request_arg(fn):
    sig = inspect.signature(fn)
    params = sig.parameters
    found = False
    for name, param in params.items():
        if name == 'request':
            found = True
            continue
        if found and (param.kind != inspect.Parameter.VAR_POSITIONAL and param.kind != inspect.Parameter.KEYWORD_ONLY and param.kind != inspect.Parameter.VAR_KEYWORD):
            raise ValueError('request parameter must be the last named parameter in function: %s%s' % (fn.__name__, str(sig)))
    return found

# RequestHandler目的就是从URL函数中分析其需要接收的参数，从request中获取必要的参数
# 调用URL函数，然后把结果转换为web.Response对象
class RequestHandler(object):
    def __init__(self, app, fn):
        self._app = app
        self._func = fn
        self._has_request_arg = has_request_arg(fn)
        self._has_var_kw_arg = has_var_kw_arg(fn)
        self._has_named_kw_args = has_named_kw_args(fn)
        self._named_kw_args = get_named_kw_args(fn)
        self._required_kw_args = get_required_kw_args(fn)

    async def __call__(self, request):
        kw = None
        if self._has_var_kw_arg or self._has_named_kw_args or self._required_kw_args:
            # 只要获取到了var 姓名 或者需要的关键字之一，就判断是不是post请求
            if request.method == 'POST':
                # 如果没有内容类型 text/html等之类的参数
                if not request.content_type:
                    return web.HTTPBadRequest(reason='Missing Content-Type.')
                ct = request.content_type.lower()     #全转小写
                # 如果内容类型是以json开头（json文件）
                if ct.startswith('application/json'):
                    params = await request.json()
                    # inspect库中的检查是否是对象的方法
                    if not isinstance(params, dict):
                        return web.HTTPBadRequest(reason='JSON body must be object.')
                    kw = params
                #判断内容类型开头是不是原生表单或者表单请求
                elif ct.startswith('application/x-www-form-urlencoded') or ct.startswith('multipart/form-data'):
                    params = await request.post()
                    kw = dict(**params)
                else:
                    # 不支持的类型
                    return web.HTTPBadRequest(reason='Unsupported Content-Type: %s' % request.content_type)
            # 判断是不是get请求
            if request.method == 'GET':
                qs = request.query_string
                if qs:
                    kw = dict()
                    for k, v in parse.parse_qs(qs, True).items():
                        kw[k] = v[0]
        # 都没有获取到任何信息
        if kw is None:
            kw = dict(**request.match_info)
        else:
            if not self._has_var_kw_arg and self._named_kw_args:
                # remove all unamed kw:
                copy = dict()
                for name in self._named_kw_args:
                    if name in kw:
                        copy[name] = kw[name]
                kw = copy
            # check named arg:
            for k, v in request.match_info.items():
                if k in kw:
                    logging.warning('Duplicate arg name in named arg and kw args: %s' % k)
                kw[k] = v
        if self._has_request_arg:
            kw['request'] = request
        # check required kw:
        if self._required_kw_args:
            for name in self._required_kw_args:
                if not name in kw:
                    return web.HTTPBadRequest(reason='Missing argument: %s' % name)
        logging.info('call with args: %s' % str(kw))
        try:
            r = await self._func(**kw)
            return r
        except APIError as e:
            return dict(error=e.error, data=e.data, message=e.message)
        return RequestHandler.__call__()

def add_static(app):
    path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
    app.router.add_static('/static/', path)
    logging.info('add static %s => %s' % ('/static/', path))


# 改变静态网址下路径
def add_static(app):
    path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static')
    app.router.add_static('/static/', path)
    logging.info('add static %s => %s' % ('/static/', path))

# add_route函数，用来注册一个URL处理函数
def add_route(app, fn):
    # getattr() 函数用于返回一个对象属性值。
    # 类型
    # 路径
    method = getattr(fn, '__method__', None)
    path = getattr(fn, '__route__', None)
    if path is None or method is None:
        raise ValueError('@get or @post not defined in %s.' % str(fn))
    # iscoroutinefunction（）：Return True if the object is a ‎协程函数
    # isgeneratorfunction（）：Return True if the object is a ‎Python 生成器函数
    if not asyncio.iscoroutinefunction(fn) and not inspect.isgeneratorfunction(fn):
        # asyncio.coroutine（）：
        # 用来标记基于生成器的协程的装饰器。
        # 此装饰器使得旧式的基于生成器的协程能与 async/await 代码相兼容
        # 此装饰器不应该被用于 async def 协程
        fn = asyncio.coroutine(fn)
    #日志内容，inspect.signature(fn).parameters获取函数参数的参数名，参数的属性，参数的默认值
    logging.info('add route %s %s => %s(%s)' % (method, path, fn.__name__, ', '.join(inspect.signature(fn).parameters.keys())))
    app.router.add_route(method, path, RequestHandler(app, fn))

# 把很多次add_route()注册的调用
def add_routes(app, module_name):
    # Python rfind() 返回字符串最后一次出现的位置，如果没有匹配项则返回 -1。
    n = module_name.rfind('.')#对应python文件存为handles，使用rfind找到handles.py文件
    if n == (-1):#如果没有匹配项，返回（-1）
        # __import__() 函数用于动态加载类和函数 。
        # 如果一个模块经常变化就可以使用 __import__() 来动态载入。
        # globals() 函数会以字典类型返回当前位置的全部全局变量。
        # locals() 函数会以字典类型返回当前位置的全部局部变量。
        mod = __import__(module_name, globals(), locals())
    else:#找到了，返回字符串出现的最后一次位置。
        name = module_name[n+1:]
        mod = getattr(__import__(module_name[:n], globals(), locals(), [name]), name)
    for attr in dir(mod):
        if attr.startswith('_'):
            continue
        fn = getattr(mod, attr)
        # callable() 函数用于检查一个对象是否是可调用的。如果返回 True，object 仍然可能调用失败；但如果返回 False，调用对象 object 绝对不会成功。
        # 对于函数、方法、lambda 函式、 类以及实现了 __call__ 方法的类实例, 它都返回 True。
        if callable(fn):#如果找到了对应的处理方法。
            method = getattr(fn, '__method__', None)
            path = getattr(fn, '__route__', None)
            if method and path:
                # logging.info('add rosute ')
                add_route(app, fn)