package org.nutz.mvc.impl;

import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.nutz.http.Request;
import org.nutz.http.Sender;
import org.nutz.http.Request.METHOD;
import org.nutz.http.Response;
import org.nutz.json.Json;
import org.nutz.lang.Each;
import org.nutz.lang.Invoking;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.lang.util.NutMap;

/**
 * JSON-RPC 简单封装
 * <p/>
 * <code>@At<p/>@Ok("json")<p/>public NutMap jsonrpc(Reader r){<p/>return JsonRPC.invoke(this);<p/>}</code>
 *
 * @author wendal
 * @since 1.r.56
 *
 */
@SuppressWarnings("unchecked")
public class JsonRPC {

    public static final int ParseError = -32700;
    public static final int InvalidRequest = -32600;
    public static final int MethodNotFound = -32601;
    public static final int InvalidParams = -32602;
    public static final int InternalError = -32603;
    public static final int ServerError = -32000;
    public static final String Version = "2.0";

    /**
     * 服务端
     */
    public static NutMap invoke(final Object obj, Reader r) {
        final NutMap resp = new NutMap();
        resp.setv("jsonrpc", Version);
        Object req;
        try {
            req = Json.fromJson(r);
        } catch (Exception e) {
            return resp.setv("error", error(ParseError, "Parse error", E(e)));
        }
        if (req == null) {
            return resp.setv("error", error(InvalidRequest, "Invalid Request", "json is null"));
        }

        if (req instanceof Iterable) {// rpc批量调用
            final List<NutMap> results = new ArrayList<NutMap>();
            Lang.each(req, new Each<Object>() {
                public void invoke(int index, Object ele, int length) {
                    if (ele instanceof Map) {
                        results.add(JsonRPC.invoke(obj, new NutMap((Map<String, Object>) ele)));
                    } else {
                        NutMap _resp = new NutMap();
                        _resp.setv("jsonrpc", Version).setv("error", error(InvalidRequest, "Invalid Request", "not map or list"));
                        results.add(_resp);
                    }
                }
            });
            return resp.setv("result", results);
        } else if (req instanceof Map) { // 单一调用
            return invoke(obj, new NutMap((Map<String, Object>) req));
        } else { // 传的是什么鸟,拒绝
            return resp.setv("error", error(InvalidRequest, "Invalid Request", "not map or list"));
        }
    }

    public static NutMap invoke(Object obj, NutMap req) {
        NutMap resp = new NutMap();
        String version = req.getString("jsonrpc", Version);
        String id = req.getString("id");
        resp.setv("id", id);
        resp.setv("jsonrpc", version);
        String method = req.getString("method");
        if (Strings.isBlank(method)) {
            return resp.setv("error", error(InvalidRequest, "Invalid Request", "method name is blank"));
        }
        List<Object> params = req.getList("params", Object.class, Collections.EMPTY_LIST);
        Invoking ing;
        try {
            ing = Mirror.me(obj).getInvoking(method, params.toArray());
        } catch (Exception e) {
            return resp.setv("error", error(MethodNotFound, "Method not found", E(e)));
        }
        try {
            return resp.setv("result", ing.invoke(obj));
        } catch (Exception e) {
            return resp.setv("error", error(ServerError, e.getMessage(), E(e)));
        } catch (Throwable e) {
            StringWriter sw = new StringWriter();
            PrintWriter writer = new PrintWriter(sw);
            e.printStackTrace(writer);
            return resp.setv("error", error(InternalError, e.getMessage(), E(e)));
        }
    }

    protected static NutMap error(int code, String message, Object data) {
        return new NutMap().setv("code", code).setv("message", message).setv("data", data);
    }

    protected static String E(Throwable e) {
        StringWriter sw = new StringWriter();
        PrintWriter writer = new PrintWriter(sw);
        e.printStackTrace(writer);
        return sw.toString();
    }

    /**
     * 客户端. 用于生成一个代理接口的实例,透明访问json-rpc服务
     * @param klass 需要代理的接口
     * @param endpoint jsonrpc URL入口
     * @param namespace 命名空间,非json-rpc标准,扩展用,不需要就传null
     * @param timeout 超时设置,若永不超时,设置为-1
     * @return 代理实例
     */
    public static <T> T mapper(Class<T> klass, final String endpoint, final String namespace, final int timeout) {
        return (T)Proxy.newProxyInstance(klass.getClassLoader(), new Class<?>[]{klass}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                NutMap jreq = new NutMap();
                jreq.setv("jsonrpc", "2.0").setv("id", R.UU32()).setv("method", method.getName());
                if (!Strings.isBlank(namespace)) {
                    jreq.put("namespace", namespace);
                }
                jreq.setv("params", args);
                Request req = Request.create(endpoint, METHOD.POST);
                req.setData(Json.toJson(jreq));
                Response resp = Sender.create(req).setTimeout(timeout).send();
                if (resp.isOK()) {
                    if (method.getReturnType() == Void.class)
                        return null;
                    return Json.fromJson(method.getGenericReturnType(), resp.getReader());
                }
                throw new RuntimeException("resp code="+resp.getStatus());
            }
        });
    }
}
