package chat.rocket.android.helper;

import android.util.Patterns;
import bolts.Continuation;
import bolts.Task;
import chat.rocket.android.model.MethodCall;
import chat.rocket.android.model.ServerConfig;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Utility class for creating/handling MethodCall.
 */
public class MethodCallHelper {

  private final String serverConfigId;

  public MethodCallHelper(String serverConfigId) {
    this.serverConfigId = serverConfigId;
  }

  private Task<JSONObject> injectErrorHandler(Task<JSONObject> task) {
    return task.continueWithTask(_task -> {
      if (_task.isFaulted()) {
        Exception exception = _task.getError();
        if (exception instanceof MethodCall.Error) {
          String errMessage = new JSONObject(exception.getMessage()).getString("message");
          return Task.forError(new Exception(errMessage));
        } else {
          return Task.forError(exception);
        }
      } else {
        return _task;
      }
    });
  }

  private interface ParamBuilder {
    void buildParam(JSONObject param) throws JSONException;
  }

  private <T> Task<T> call(String methodName,
      Continuation<JSONObject, Task<T>> onSuccess) {
    return injectErrorHandler(MethodCall.execute(serverConfigId, methodName, null))
        .onSuccessTask(onSuccess);
  }

  private <T> Task<T> call(String methodName, ParamBuilder paramBuilder,
      Continuation<JSONObject, Task<T>> onSuccess) {
    JSONObject param = new JSONObject();

    try {
      paramBuilder.buildParam(param);
    } catch (JSONException exception) {
      return Task.forError(exception);
    }

    return injectErrorHandler(MethodCall.execute(serverConfigId, methodName, param.toString()))
        .onSuccessTask(onSuccess);
  }

  /**
   * Register User.
   */
  public Task<Void> registerUser(final String name, final String email,
      final String password, final String confirmPassword) {
    return call("registerUser", param -> param
        .put("name", name)
        .put("email", email)
        .put("pass", password)
        .put("confirm-pass", confirmPassword),
        task -> Task.forResult(null)); // nothing to do.
  }

  private Continuation<String, Task<Void>> saveToken() {
    return task -> RealmHelperBolts.executeTransaction(realm ->
        realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
            .put("id", serverConfigId)
            .put("token", task.getResult())
            .put("tokenVerified", true)));
  }

  /**
   * Login with username/email and password.
   */
  public Task<Void> loginWithEmail(final String usernameOrEmail, final String password) {
    return call("login", param -> {
      if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
        param.put("user", new JSONObject().put("email", usernameOrEmail));
      } else {
        param.put("user", new JSONObject().put("username", usernameOrEmail));
      }
      param.put("password", new JSONObject()
          .put("digest", CheckSum.sha256(password))
          .put("algorithm", "sha-256"));
    }, task -> Task.forResult(task.getResult().getString("token"))).onSuccessTask(saveToken());
  }

  /**
   * Login with GitHub OAuth.
   */
  public Task<Void> loginWithGitHub(final String credentialToken,
      final String credentialSecret) {
    return call("login", param -> param
        .put("oauth", new JSONObject()
            .put("credentialToken", credentialToken)
            .put("credentialSecret", credentialSecret)),
        task -> Task.forResult(task.getResult().getString("token"))).onSuccessTask(saveToken());
  }

  /**
   * Login with token.
   */
  public Task<Void> loginWithToken(final String token) {
    return call("login", param -> param.put("resume", token),
        task -> Task.forResult(task.getResult().getString("token"))).onSuccessTask(saveToken());
  }

  /**
   * Logout.
   */
  public Task<Void> logout() {
    return call("logout", task -> Task.forResult(null));
  }
}

@@ -198,16 +198,93 @@
 Config;%0A
+import chat.rocket.android.ws.RocketChatWebSocketAPI;%0Aimport java.util.UUID;%0A
 import j
@@ -342,16 +342,16 @@
 rBolts;%0A
-
 import o
@@ -455,16 +455,23 @@
 thodCall
+ or RPC
 .%0A */%0Apu
@@ -539,16 +539,60 @@
 onfigId;
+%0A  private final RocketChatWebSocketAPI api;
 %0A%0A  publ
@@ -678,16 +678,518 @@
 nfigId;%0A
+    api = null;%0A  %7D%0A%0A  public MethodCallHelper(String serverConfigId, RocketChatWebSocketAPI api) %7B%0A    this.serverConfigId = serverConfigId;%0A    this.api = api;%0A  %7D%0A%0A  private Task%3CJSONObject%3E executeMethodCall(String methodName, String param) %7B%0A    if (api != null) %7B%0A      return api.rpc(UUID.randomUUID().toString(), methodName, param)%0A          .onSuccessTask(task -%3E Task.forResult(task.getResult().result));%0A    %7D else %7B%0A      return MethodCall.execute(serverConfigId, methodName, param);%0A    %7D%0A
   %7D%0A%0A  p
@@ -1931,32 +1931,39 @@
 ectErrorHandler(
+execute
 MethodCall.execu
@@ -1960,33 +1960,9 @@
 Call
-.execute(serverConfigId, 
+(
 meth
@@ -2348,16 +2348,23 @@
 Handler(
+execute
 MethodCa
@@ -2369,33 +2369,9 @@
 Call
-.execute(serverConfigId, 
+(
 meth

modify MethodCallHelper to enable to use RocketChatWebSocketAPI#rpc directory.