package org.forks.jsonrpc;

import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

public abstract class JsonRpcClient {
    private final String mMethod;
    private final static String LOG_TAG = "JsonRpcClient";
    private long requestTime;

    protected JsonRpcClient(String method) {
        mMethod = method;
    }

    public final JsonRpcResult call(Object... args) throws JsonRpcException {
        try {
            JSONObject req = newRequest(args);
            JSONObject res = request(req);
            checkResponseId(req, res);
            checkError(res);
            return new JsonRpcResult(res.get("result"));
        } catch (JSONException e) {
            Log.w(LOG_TAG, "", e);
            throw new JsonRpcException(e);
        }
        catch (JsonRpcException e){
            throw e;
        }
        catch (Exception e){
            throw new JsonRpcException(JsonRpcException.NETWORK_ERROR_CODE,e.getMessage());
        }
    }

    private static void checkResponseId(JSONObject req, JSONObject res)
            throws JSONException, JsonRpcException {
        int reqId = req.getInt("id");
        int resId = res.getInt("id");
        if (reqId != resId) {
            throw new JsonRpcException(String.format(
                    "response id (%d) does not match to request id (%d)",
                    reqId, resId));
        }
    }

    private static void checkError(JSONObject res) throws JSONException,
            JsonRpcException {
        JSONObject err = res.optJSONObject("error");
        if (err != null) {
            int code = err.getInt("code");
            String msg = err.getString("message");
            String trace = err.optString("detail");
            throw new JsonRpcException(code, msg, trace);
        }
    }

    private JSONObject request(JSONObject req) throws JsonRpcException {
        String json = req.toString();
        log_request(json);
        String jsonBack = doRequest(json);
        log_response(jsonBack);
        return Helper.parseJsonObject(jsonBack);
    }

    private void log_response(String jsonBack) {
        long duration = System.currentTimeMillis() - requestTime;
        Log.i(LOG_TAG, String.format("get response using %d ms, response: %s",
                duration, jsonBack));
    }

    private void log_request(String reqJson) {
        requestTime = System.currentTimeMillis();
        Log.v(LOG_TAG, "start request: " + reqJson);
    }

    protected abstract String doRequest(String json) throws JsonRpcException;

    private JSONObject newRequest(Object[] args) throws JSONException {
        Object params = RpcTypeParser.toJson(args);
        return newRequest(params);
    }

    private static int mIdSeed;

    private JSONObject newRequest(Object params) throws JSONException {
        JSONObject result = new JSONObject();
        result.put("id", mIdSeed++);
        result.put("method", mMethod);
        result.put("params", params);
        return result;
    }
}
