# coding=utf-8
from __future__ import unicode_literals, print_function, absolute_import


import six

from django.conf import settings

from .decorators import bind_context
from rpc.exceptions import GaiaRPCFaultException
from rpc.tool.error_code import CODES


@bind_context('rpc/vendor/ping')
def ping(ctx):
    return 'pong'


@bind_context('rpc/vendor/echo')
def echo(ctx, payload):
    return payload


@bind_context('rpc/vendor/make_server_messages', return_data_only=False)
def echo_server_messages(ctx, messages):
    return {
        'debug': {
            'messages': list(messages),
        },
        'error': 0,
        'message': '',
        'data': None,
    }


@bind_context('rpc/vendor/deprecated', deprecated=True)
def deprecated(ctx):
    pass


@bind_context('rpc/vendor/fault_exception')
def fault_exception(ctx, error, message=None):
    assert isinstance(error, six.integer_types)
    if message is None and error == 0 or error >= 10000:
        message = CODES.getDesc(error)
    if message is None:
        message = '::Unknown Error Code'
    raise GaiaRPCFaultException(error=error, message=message, data=None)


@bind_context('rpc/vendor/logger_app')
def logger_app(ctx, message=None):
    ctx.logger.app(message=message)


@bind_context('rpc/vendor/nested/call')
def nested_call(ctx, method, params=None):
    params = params or {}
    return ctx.gaia_local[method](**params).unwrap()


@bind_context('rpc/vendor/nested/repeat_call')
def nested_repeat_call(ctx, count, method, params=None):
    params = params or {}
    for _ in range(count):
        ctx.gaia_local[method](**params).unwrap()


@bind_context('rpc/vendor/rpc_remote/call')
def nested_call(ctx, method, params=None):
    if not settings.DEBUG:
        return
    params = params or {}
    return ctx.rpc_remote[method](**params).unwrap()


@bind_context('rpc/vendor/profiling/line_profiler')
def run_line_profiler(ctx, method, params=None):
    params = params or {}

    if not settings.DEBUG:
        return

    from six import StringIO
    import line_profiler
    p = line_profiler.LineProfiler()

    import os
    import rpc
    base_dir = os.path.normpath(os.path.dirname(os.path.dirname(rpc.__file__))) + '/'
    assert os.path.isabs(base_dir)
    assert os.path.isdir(base_dir)

    import inspect
    object_id_map = {}
    object_stack = []

    def push_object(o):
        if o is object_id_map:
            return
        if o is object_stack:
            return
        if isinstance(o, six.string_types + six.integer_types):
            return
        id_o = id(o)
        if id_o in object_id_map:
            return
        object_id_map[id_o] = o
        object_stack.append(o)

    def do_add_function(f):
        code = f.__code__
        path = os.path.normpath(code.co_filename)
        if not os.path.exists(path):
            return
        assert os.path.isabs(path)
        if os.path.commonprefix([base_dir, path]) != base_dir:
            return
        p.add_function(f)

    def add_object(o):
        if inspect.isfunction(o):
            do_add_function(o)
            if o.func_closure:
                for cell in o.func_closure:
                    push_object(cell.cell_contents)
        if isinstance(o, (list, tuple)):
            push_iter(o)
        if isinstance(o, dict):
            push_dict(o)
        if isinstance(o, property):
            push_iter([o.fget, o.fset, o.fdel])
        for key in ['__globals__', '__class__']:
            if hasattr(o, key):
                push_object(getattr(o, key))
        for key in ['__dict__']:
            if hasattr(o, key):
                push_dict(getattr(o, key))

    def push_dict(d):
        push_iter(d.keys())
        push_iter(d.values())

    def push_iter(it):
        for o in iter(it):
            push_object(o)

    def clear_stack():
        while object_stack:
            f = object_stack.pop()
            add_object(f)

    from .api_control import MethodInfo
    from .instance import api_manager
    for method_name in api_manager.get_method_list():
        method_info = api_manager.get_method_info(method_name)
        assert isinstance(method_info, MethodInfo)
        push_object(method_info.func)

    import api.models
    push_object(api.models)

    clear_stack()

    def func():
        try:
            ctx.gaia_local[method](**params).unwrap()
        except Exception:
            pass

    p.runcall(func)

    string_stream = StringIO()
    p.print_stats(stream=string_stream, stripzeros=True)

    string_stream.seek(0)
    report = string_stream.read()
    return {
        'report': report,
    }

