package com.mobify.astro.messaging;

import android.support.annotation.NonNull;
import android.util.Log;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Exception;

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

public class RpcResponse extends RpcMessage {
    private static final String TAG = RpcMessage.class.getName();

    private String address;
    private boolean sent = false;

    static final class KeyNames {
        static final String RESULT = "result";
        static final String ERROR = "error";
        static final String ERROR_MESSAGE = "message";
        static final String ERROR_DETAILS = "details";
        static final String ERROR_STACK_TRACE = "stacktrace";
    }

    protected RpcResponse() { super(); }

    /**
     * Creates a new RpcResponse object targeted at address, with sender and callId as specified.
     *
     * @param address An address in the messaging system
     * @param sender a pointer to an object that will be the origin of this message
     * @param id the callId of the request this is a response for.
     * @throws org.json.JSONException
     */
    public RpcResponse(@NonNull String address, AddressableObject sender, String id) throws JSONException {
        super(new JSONObject(), sender, id);
        JSONObject payload = getPayload();
        payload.put(KeyNames.RESULT, new JSONObject());
        payload.put(KeyNames.ERROR, JSONObject.NULL);
        this.address = address;
    }

    /**
     * Set the result of this RpcResponse
     *
     * @param result A JSON serializable object representing the packed response of this RPC call
     * @throws org.json.JSONException
     */
    public void setResult(Object result) throws JSONException {
        JSONObject payload = getPayload();
        payload.put(KeyNames.RESULT, result);
    }

    /**
     * Adds the message of the given error to the response payload
     *
     * @param error An exception to be reported back to the rpc caller
     */
    public void setError(@NonNull Exception error) {
        try {
            JSONObject errorValue = new JSONObject();
            errorValue.put(KeyNames.ERROR_MESSAGE, error.toString());

            String errorDetail = getErrorDetailMessage(error.getCause());
            if (errorDetail.isEmpty()) {
                errorDetail = "none";
            }
            errorValue.put(KeyNames.ERROR_DETAILS, errorDetail);

            String stackTrace = getErrorStackTrace(error);
            if (stackTrace != null && !stackTrace.isEmpty()) {
                errorValue.put(KeyNames.ERROR_STACK_TRACE, stackTrace);
            }

            JSONObject payload = getPayload();
            payload.put(KeyNames.ERROR, errorValue);
            payload.put(KeyNames.RESULT, JSONObject.NULL);
        } catch (JSONException e) {
            Log.e(TAG, e.getMessage(), e);
        }
    }

    /**
     * Goes down the chain of Throwable causes and collects details into string
     */
    private String getErrorDetailMessage(Throwable cause) {
        int remainingRecursionDepth = 20;
        StringBuilder messageBuilder = new StringBuilder();
        while (cause != null && remainingRecursionDepth > 0) {
            messageBuilder.append("\t").append(cause.toString()).append("\n");
            cause = cause.getCause();
            remainingRecursionDepth--;
        }
        return messageBuilder.toString();
    }

    /**
     * Returns stack trace for a given throwable
     *
     * Note: If we ever add apache commons or guava this is in there already.
     */
    private String getErrorStackTrace(Throwable error) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        error.printStackTrace(pw);
        return sw.toString();
    }

    protected String getErrorMessage() throws JSONException {
        return getPayload().getJSONObject(KeyNames.ERROR).getString(KeyNames.ERROR_MESSAGE);
    }

    public boolean isSent() {
        return sent;
    }

    public void responseSent() {
        sent = true;
    }

    public String getDestinationAddress() {
        return address;
    }
}
