package org.thoughtcrime.securesms;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.MimeTypeMap;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.ActionBar;
import androidx.core.app.TaskStackBuilder;
import androidx.core.content.pm.ShortcutInfoCompat;
import androidx.core.content.pm.ShortcutManagerCompat;
import androidx.core.graphics.drawable.IconCompat;

import com.b44t.messenger.DcChat;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import com.b44t.messenger.DcMsg;
import com.b44t.messenger.rpc.Rpc;
import com.b44t.messenger.rpc.RpcException;
import com.google.common.base.Charsets;

import org.json.JSONObject;
import org.thoughtcrime.securesms.connect.AccountManager;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.Util;

import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate  {
  private static final String TAG = WebxdcActivity.class.getSimpleName();
  private static final String EXTRA_ACCOUNT_ID = "accountId";
  private static final String EXTRA_APP_MSG_ID = "appMessageId";
  private static final String EXTRA_HIDE_ACTION_BAR = "hideActionBar";
  private static final String EXTRA_HREF = "href";
  private static final int REQUEST_CODE_FILE_PICKER = 51426;

  private ValueCallback<Uri[]> filePathCallback;
  private DcContext dcContext;
  private Rpc rpc;
  private DcMsg dcAppMsg;
  private String baseURL;
  private String sourceCodeUrl = "";
  private String selfAddr;
  private int sendUpdateMaxSize;
  private int sendUpdateInterval;
  private boolean internetAccess = false;
  private boolean hideActionBar = false;

  public static void openMaps(Context context, int chatId) {
    openMaps(context, chatId, "");
  }

  public static void openMaps(Context context, int chatId, String href) {
    DcContext dcContext = DcHelper.getContext(context);
    int msgId = 0;
    final int mapsVersion = 2;
    if (dcContext.getConfigInt("ui.maps_version") >= mapsVersion) {
      msgId = dcContext.initWebxdcIntegration(chatId);
    }
    if (msgId == 0) {
      try {
        InputStream inputStream = context.getResources().getAssets().open("webxdc/maps.xdc");
        String outputFile = DcHelper.getBlobdirFile(dcContext, "maps", ".xdc");
        Util.copy(inputStream, new FileOutputStream(outputFile));
        dcContext.setWebxdcIntegration(outputFile);
        msgId = dcContext.initWebxdcIntegration(chatId);
      } catch (IOException e) {
        e.printStackTrace();
      }
      if (msgId == 0) {
         Toast.makeText(context, "Cannot get maps.xdc, see log for details.", Toast.LENGTH_LONG).show();
         return;
      }
      dcContext.setConfigInt("ui.maps_version", mapsVersion);
    }
    openWebxdcActivity(context, msgId, true, href);
  }

  public static void openWebxdcActivity(Context context, DcMsg instance) {
    openWebxdcActivity(context, instance, "");
  }

  public static void openWebxdcActivity(Context context, DcMsg instance, String href) {
    openWebxdcActivity(context, instance.getId(), false, href);
  }

  public static void openWebxdcActivity(Context context, int msgId, boolean hideActionBar, String href) {
    if (!Util.isClickedRecently()) {
      if (Prefs.isDeveloperModeEnabled(context)) {
        WebView.setWebContentsDebuggingEnabled(true);
      }
      context.startActivity(getWebxdcIntent(context, msgId, hideActionBar, href));
    }
  }

  private static Intent getWebxdcIntent(Context context, int msgId, boolean hideActionBar, String href) {
    DcContext dcContext = DcHelper.getContext(context);
    Intent intent = new Intent(context, WebxdcActivity.class);
    intent.setAction(Intent.ACTION_VIEW);
    intent.putExtra(EXTRA_ACCOUNT_ID, dcContext.getAccountId());
    intent.putExtra(EXTRA_APP_MSG_ID, msgId);
    intent.putExtra(EXTRA_HIDE_ACTION_BAR, hideActionBar);
    intent.putExtra(EXTRA_HREF, href);
    return intent;
  }

  private static Intent[] getWebxdcIntentWithParentStack(Context context, int msgId) {
    DcContext dcContext = DcHelper.getContext(context);

    final Intent chatIntent = new Intent(context, ConversationActivity.class)
      .putExtra(ConversationActivity.CHAT_ID_EXTRA, dcContext.getMsg(msgId).getChatId())
      .setAction(Intent.ACTION_VIEW);

    final Intent webxdcIntent = getWebxdcIntent(context, msgId, false, "");

    return TaskStackBuilder.create(context)
      .addNextIntentWithParentStack(chatIntent)
      .addNextIntent(webxdcIntent)
      .getIntents();
  }

  @Override
  protected void onCreate(Bundle state, boolean ready) {
    super.onCreate(state, ready);
    rpc = DcHelper.getRpc(this);

    Bundle b = getIntent().getExtras();
    hideActionBar = b.getBoolean(EXTRA_HIDE_ACTION_BAR, false);

    webView.setWebChromeClient(new WebChromeClient() {
      @Override
      @RequiresApi(21)
      public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) {
        if (WebxdcActivity.this.filePathCallback != null) {
          WebxdcActivity.this.filePathCallback.onReceiveValue(null);
        }
        WebxdcActivity.this.filePathCallback = filePathCallback;
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, fileChooserParams.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE);
        WebxdcActivity.this.startActivityForResult(Intent.createChooser(intent, getString(R.string.select)), REQUEST_CODE_FILE_PICKER);
        return true;
      }
    });

    DcEventCenter eventCenter = DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
    eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
    eventCenter.addObserver(DcContext.DC_EVENT_MSGS_CHANGED, this);
    eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_REALTIME_DATA, this);
    
    int appMessageId = b.getInt(EXTRA_APP_MSG_ID);
    int accountId = b.getInt(EXTRA_ACCOUNT_ID);
    this.dcContext = DcHelper.getContext(getApplicationContext());
    if (accountId != dcContext.getAccountId()) {
      AccountManager.getInstance().switchAccount(getApplicationContext(), accountId);
      this.dcContext = DcHelper.getContext(getApplicationContext());
    }

    this.dcAppMsg = this.dcContext.getMsg(appMessageId);
    if (!this.dcAppMsg.isOk()) {
      Toast.makeText(this, "Webxdc does no longer exist.", Toast.LENGTH_LONG).show();
      finish();
      return;
    }

    // `msg_id` in the subdomain makes sure, different apps using same files do not share the same cache entry
    // (WebView may use a global cache shared across objects).
    // (a random-id would also work, but would need maintenance and does not add benefits as we regard the file-part interceptRequest() only,
    // also a random-id is not that useful for debugging)
    this.baseURL = "https://acc" + dcContext.getAccountId() + "-msg" + appMessageId + ".localhost";

    final JSONObject info = this.dcAppMsg.getWebxdcInfo();
    internetAccess = JsonUtils.optBoolean(info, "internet_access");
    if ("landscape".equals(JsonUtils.optString(info, "orientation"))) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    } else {
        // enter fullscreen mode if necessary,
        // this is needed here because if the app is opened while already in landscape mode, onConfigurationChanged() is not triggered
        setScreenMode(getResources().getConfiguration());
    }
    selfAddr = info.optString("self_addr");
    sendUpdateMaxSize = info.optInt("send_update_max_size");
    sendUpdateInterval = info.optInt("send_update_interval");

    toggleFakeProxy(!internetAccess);

    WebSettings webSettings = webView.getSettings();
    webSettings.setJavaScriptEnabled(true);
    webSettings.setAllowFileAccess(false);
    webSettings.setBlockNetworkLoads(!internetAccess);
    webSettings.setAllowContentAccess(false);
    webSettings.setGeolocationEnabled(false);
    webSettings.setAllowFileAccessFromFileURLs(false);
    webSettings.setAllowUniversalAccessFromFileURLs(false);
    webSettings.setDatabaseEnabled(true);
    webSettings.setDomStorageEnabled(true);
    webView.setNetworkAvailable(internetAccess); // this does not block network but sets `window.navigator.isOnline` in js land
    webView.addJavascriptInterface(new InternalJSApi(), "InternalJSApi");

    String extraHref = b.getString(EXTRA_HREF, "");
    if (TextUtils.isEmpty(extraHref)) {
      extraHref = "index.html";
    }

    String href = baseURL + "/" + extraHref;
    String encodedHref = "";
    try {
      encodedHref = URLEncoder.encode(href, Charsets.UTF_8.name());
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }

    webView.loadUrl(this.baseURL + "/webxdc_bootstrap324567869.html?i=1&href=" + encodedHref);

    Util.runOnAnyBackgroundThread(() -> {
      final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
      Util.runOnMain(() -> {
        updateTitleAndMenu(info, chat);
      });
    });
  }

  @Override
  public void onResume() {
    super.onResume();
    DcHelper.getNotificationCenter(this).updateVisibleWebxdc(dcContext.getAccountId(), dcAppMsg.getId());
  }

  @Override
  protected void onPause() {
    super.onPause();
    DcHelper.getNotificationCenter(this).clearVisibleWebxdc();
  }

  @Override
  protected void onDestroy() {
    DcHelper.getEventCenter(this.getApplicationContext()).removeObservers(this);
    leaveRealtimeChannel();
    super.onDestroy();
  }

  @Override
  public boolean onPrepareOptionsMenu(Menu menu) {
    // do not call super.onPrepareOptionsMenu() as the default "Search" menu is not needed
    menu.clear();
    this.getMenuInflater().inflate(R.menu.webxdc, menu);
    menu.findItem(R.id.source_code).setVisible(!sourceCodeUrl.isEmpty());
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    super.onOptionsItemSelected(item);
    switch (item.getItemId()) {
      case R.id.menu_add_to_home_screen:
        addToHomeScreen(this, dcAppMsg.getId());
        return true;
      case R.id.source_code:
        openUrlInBrowser(this, sourceCodeUrl);
        return true;
      case R.id.show_in_chat:
        showInChat();
        return true;
    }
    return false;
  }

  @Override
  public void onConfigurationChanged(Configuration newConfig) {
    Log.i(TAG, "onConfigurationChanged(" + newConfig.orientation + ")");
    super.onConfigurationChanged(newConfig);
    // orientation might have changed, enter/exit fullscreen mode if needed
    setScreenMode(newConfig);
  }

  private void setScreenMode(Configuration config) {
    // enter/exit fullscreen mode depending on orientation (landscape/portrait),
    // on tablets there is enough height so fullscreen mode is never enabled there
    boolean enable = config.orientation == Configuration.ORIENTATION_LANDSCAPE && !getResources().getBoolean(R.bool.isBigScreen);
    getWindow().getDecorView().setSystemUiVisibility(enable? View.SYSTEM_UI_FLAG_FULLSCREEN : 0);
    ActionBar actionBar = getSupportActionBar();
    if (actionBar != null) {
      if (hideActionBar || enable) {
        actionBar.hide();
      } else {
        actionBar.show();
      }
    }
  }

  // This is usually only called when internetAccess == true or for mailto/openpgp4fpr scheme,
  // because when internetAccess == false, the page is loaded inside an iframe,
  // and WebViewClient.shouldOverrideUrlLoading is not called for HTTP(S) links inside the iframe
  @Override
  protected boolean openOnlineUrl(String url) {
    Log.i(TAG, "openOnlineUrl: " + url);
    boolean openExternally = !internetAccess && !url.startsWith(baseURL +"/");
    if (openExternally || url.startsWith("mailto:") || url.startsWith("openpgp4fpr:")) {
      return super.openOnlineUrl(url);
    }
    return false; // continue loading in the WebView
  }

  @Override
  protected WebResourceResponse interceptRequest(String rawUrl) {
    Log.i(TAG, "interceptRequest: " + rawUrl);
    WebResourceResponse res = null;
    try {
      if (rawUrl == null) {
        throw new Exception("no url specified");
      }
      String path = Uri.parse(rawUrl).getPath();
      if (path.equalsIgnoreCase("/webxdc.js")) {
        InputStream targetStream = getResources().openRawResource(R.raw.webxdc);
        res = new WebResourceResponse("text/javascript", "UTF-8", targetStream);
      } else if (path.equalsIgnoreCase("/webxdc_bootstrap324567869.html")) {
        InputStream targetStream = getResources().openRawResource(R.raw.webxdc_wrapper);
        res = new WebResourceResponse("text/html", "UTF-8", targetStream);
      } else if (path.equalsIgnoreCase("/sandboxed_iframe_rtcpeerconnection_check_5965668501706.html")) {
        InputStream targetStream = getResources().openRawResource(R.raw.sandboxed_iframe_rtcpeerconnection_check);
        res = new WebResourceResponse("text/html", "UTF-8", targetStream);
      } else {
        byte[] blob = this.dcAppMsg.getWebxdcBlob(path);
        if (blob == null) {
          if (internetAccess) {
            return null; // do not intercept request
          }
          throw new Exception("\"" + path + "\" not found");
        }
        String ext = MediaUtil.getFileExtensionFromUrl(path);
        String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
        if (mimeType == null) {
          switch (ext) {
            case "js": mimeType = "text/javascript"; break;
            case "wasm": mimeType = "application/wasm"; break;
            default:   mimeType = "application/octet-stream"; Log.i(TAG, "unknown mime type for " + rawUrl); break;
          }
        }
        String encoding = mimeType.startsWith("text/")? "UTF-8" : null;
        InputStream targetStream = new ByteArrayInputStream(blob);
        res = new WebResourceResponse(mimeType, encoding, targetStream);
      }
    } catch (Exception e) {
      e.printStackTrace();
      InputStream targetStream = new ByteArrayInputStream(("Webxdc Request Error: " + e.getMessage()).getBytes());
      res = new WebResourceResponse("text/plain", "UTF-8", targetStream);
    }

    if (!internetAccess) {
      Map<String, String> headers = new HashMap<>();
      headers.put("Content-Security-Policy",
          "default-src 'self'; "
        + "style-src 'self' 'unsafe-inline' blob: ; "
        + "font-src 'self' data: blob: ; "
        + "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob: ; "
        + "connect-src 'self' data: blob: ; "
        + "img-src 'self' data: blob: ; "
        + "media-src 'self' data: blob: ;"
        + "webrtc 'block' ; "
      );
      headers.put("X-DNS-Prefetch-Control", "off");
      res.setResponseHeaders(headers);
    }
    return res;
  }

  private void callJavaScriptFunction(String func) {
    webView.evaluateJavascript("document.getElementById('frame').contentWindow." + func + ";", null);
  }

  @Override
  public void handleEvent(@NonNull DcEvent event) {
    int eventId = event.getId();
    if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE && event.getData1Int() == dcAppMsg.getId())) {
      Log.i(TAG, "handling status update event");
      callJavaScriptFunction("__webxdcUpdate()");
    } else if ((eventId == DcContext.DC_EVENT_WEBXDC_REALTIME_DATA && event.getData1Int() == dcAppMsg.getId())) {
      Log.i(TAG, "handling realtime data event");
      StringBuilder data = new StringBuilder();
      for (byte b : event.getData2Blob()) {
          data.append(((int) b)  + ",");
      }
      callJavaScriptFunction("__webxdcRealtimeData([" + data + "])");
    } else if ((eventId == DcContext.DC_EVENT_MSGS_CHANGED && event.getData2Int() == dcAppMsg.getId())) {
      this.dcAppMsg = this.dcContext.getMsg(event.getData2Int()); // msg changed, reload data from db
      Util.runOnAnyBackgroundThread(() -> {
        final JSONObject info = dcAppMsg.getWebxdcInfo();
        final DcChat chat = dcContext.getChat(dcAppMsg.getChatId());
        Util.runOnMain(() -> {
          updateTitleAndMenu(info, chat);
        });
      });
    }
  }

  private void updateTitleAndMenu(JSONObject info, DcChat chat) {
      final String docName = JsonUtils.optString(info, "document");
      final String xdcName = JsonUtils.optString(info, "name");
      final String currSourceCodeUrl = JsonUtils.optString(info, "source_code_url");
      getSupportActionBar().setTitle((docName.isEmpty() ? xdcName : docName) + " – " + chat.getName());
      if (!sourceCodeUrl.equals(currSourceCodeUrl)) {
        sourceCodeUrl = currSourceCodeUrl;
        invalidateOptionsMenu();
      }
  }

  private void showInChat() {
    Intent intent = new Intent(this, ConversationActivity.class);
    intent.putExtra(ConversationActivity.CHAT_ID_EXTRA, dcAppMsg.getChatId());
    intent.putExtra(ConversationActivity.STARTING_POSITION_EXTRA, DcMsg.getMessagePosition(dcAppMsg, dcContext));
    startActivity(intent);
  }

  public static void addToHomeScreen(Activity activity, int msgId) {
    Context context = activity.getApplicationContext();
    try {
      DcContext dcContext = DcHelper.getContext(context);
      DcMsg msg = dcContext.getMsg(msgId);
      final JSONObject info = msg.getWebxdcInfo();

      final String docName = JsonUtils.optString(info, "document");
      final String xdcName = JsonUtils.optString(info, "name");
      byte[] blob = msg.getWebxdcBlob(JsonUtils.optString(info, "icon"));
      ByteArrayInputStream is = new ByteArrayInputStream(blob);
      BitmapDrawable drawable = (BitmapDrawable) Drawable.createFromStream(is, "icon");
      Bitmap bitmap = drawable.getBitmap();

      ShortcutInfoCompat shortcutInfoCompat = new ShortcutInfoCompat.Builder(context, "xdc-" + dcContext.getAccountId() + "-" + msgId)
        .setShortLabel(docName.isEmpty() ? xdcName : docName)
        .setIcon(IconCompat.createWithBitmap(bitmap)) // createWithAdaptiveBitmap() removes decorations but cuts out a too small circle and defamiliarize the icon too much
        .setIntents(getWebxdcIntentWithParentStack(context, msgId))
        .build();

      Toast.makeText(context, R.string.one_moment, Toast.LENGTH_SHORT).show();
      if (!ShortcutManagerCompat.requestPinShortcut(context, shortcutInfoCompat, null)) {
        Toast.makeText(context, "ErrAddToHomescreen: requestPinShortcut() failed", Toast.LENGTH_LONG).show();
      }
    } catch(Exception e) {
      Toast.makeText(context, "ErrAddToHomescreen: " + e, Toast.LENGTH_LONG).show();
    }
  }

  @Override
  public void onActivityResult(int reqCode, int resultCode, final Intent data) {
    if (reqCode == REQUEST_CODE_FILE_PICKER && filePathCallback != null) {
      Uri[] dataUris = null;
      if (resultCode == Activity.RESULT_OK && data != null) {
        try {
          if (data.getDataString() != null) {
            dataUris = new Uri[]{Uri.parse(data.getDataString())};
          } else if (data.getClipData() != null) {
            final int numSelectedFiles = data.getClipData().getItemCount();
            dataUris = new Uri[numSelectedFiles];
            for (int i = 0; i < numSelectedFiles; i++) {
              dataUris[i] = data.getClipData().getItemAt(i).getUri();
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
      filePathCallback.onReceiveValue(dataUris);
      filePathCallback = null;
    }
    super.onActivityResult(reqCode, resultCode, data);
  }

  private void leaveRealtimeChannel() {
    int accountId = dcContext.getAccountId();
    int msgId = dcAppMsg.getId();
    try {
      rpc.leaveWebxdcRealtime(accountId, msgId);
    } catch (RpcException e) {
      e.printStackTrace();
    }
  }

  class InternalJSApi {
    @JavascriptInterface
    public String arcanechat() {
      return BuildConfig.VERSION_NAME;
    }

    @JavascriptInterface
    public boolean isCommunity() {
      return dcContext.isCommunity();
    }

    @JavascriptInterface
    public int sendUpdateMaxSize() {
      return WebxdcActivity.this.sendUpdateMaxSize;
    }

    @JavascriptInterface
    public int sendUpdateInterval() {
      return WebxdcActivity.this.sendUpdateInterval;
    }

    @JavascriptInterface
    public String selfAddr() {
      if (dcContext.isCommunity() && !TextUtils.isEmpty(dcContext.getCommunityUser())) {
        int flags = Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE;
        return Base64.encodeToString(dcContext.getCommunityUser().getBytes(), flags);
      }
      return WebxdcActivity.this.selfAddr;
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public String selfName() {
      if (dcContext.isCommunity() && !TextUtils.isEmpty(dcContext.getCommunityUser())) {
        return dcContext.getCommunityUser();
      }
      String name = WebxdcActivity.this.dcContext.getConfig("displayname");
      if (TextUtils.isEmpty(name)) {
        name = selfAddr();
      }
      return name;
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public boolean sendStatusUpdate(String payload) {
      Log.i(TAG, "sendStatusUpdate");
      if (!WebxdcActivity.this.dcContext.sendWebxdcStatusUpdate(WebxdcActivity.this.dcAppMsg.getId(), payload)) {
        DcChat dcChat =  WebxdcActivity.this.dcContext.getChat(WebxdcActivity.this.dcAppMsg.getChatId());
        Toast.makeText(WebxdcActivity.this,
                      dcChat.isContactRequest() ?
                          WebxdcActivity.this.getString(R.string.accept_request_first) :
                          WebxdcActivity.this.dcContext.getLastError(),
                      Toast.LENGTH_LONG).show();
        return false;
      }
      return true;
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public String getStatusUpdates(int lastKnownSerial) {
      Log.i(TAG, "getStatusUpdates");
      return WebxdcActivity.this.dcContext.getWebxdcStatusUpdates(WebxdcActivity.this.dcAppMsg.getId(), lastKnownSerial    );
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public String sendToChat(String message) {
      Log.i(TAG, "sendToChat");
      try {
        JSONObject jsonObject = new JSONObject(message);

        String text = null;
        String subject = null;
        String html = null;
        byte[] data = null;
        String name = null;
        String type = null;
        if (jsonObject.has("base64")) {
            data = Base64.decode(jsonObject.getString("base64"), Base64.NO_WRAP | Base64.NO_PADDING);
            name = jsonObject.getString("name");
        }
        if (jsonObject.has("type")) {
            type = jsonObject.getString("type");
        }
        if (jsonObject.has("text")) {
            text = jsonObject.getString("text");
        }
        if (jsonObject.has("subject")) {
            subject = jsonObject.getString("subject");
        }
        if (jsonObject.has("html")) {
            html = jsonObject.getString("html");
        }

        if (TextUtils.isEmpty(text) && TextUtils.isEmpty(subject) && TextUtils.isEmpty(html) && TextUtils.isEmpty(name)) {
            return "provided file is invalid, you need to set both name and base64 content";
        }

        DcHelper.sendToChat(WebxdcActivity.this, data, type, name, html, subject, text);
        return null;
      } catch (Exception e) {
        e.printStackTrace();
        return e.toString();
      }
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public void sendRealtimeAdvertisement() {
      int accountId = WebxdcActivity.this.dcContext.getAccountId();
      int msgId = WebxdcActivity.this.dcAppMsg.getId();
      try {
        WebxdcActivity.this.rpc.sendWebxdcRealtimeAdvertisement(accountId, msgId);
      } catch (RpcException e) {
        e.printStackTrace();
      }
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public void leaveRealtimeChannel() {
      WebxdcActivity.this.leaveRealtimeChannel();
    }

    /** @noinspection unused*/
    @JavascriptInterface
    public void sendRealtimeData(String jsonData) {
      int accountId = WebxdcActivity.this.dcContext.getAccountId();
      int msgId = WebxdcActivity.this.dcAppMsg.getId();
      try {
        Integer[] data = JsonUtils.fromJson(jsonData, Integer[].class);
        WebxdcActivity.this.rpc.sendWebxdcRealtimeData(accountId, msgId, Arrays.asList(data));
      } catch (IOException | RpcException e) {
        e.printStackTrace();
      }
    }
  }
}
