/*
 * libjingle
 * Copyright 2013, Google Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.appspot.apprtc;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;

/**
 * Java-land version of Google AppEngine's JavaScript Channel API:
 * https://developers.google.com/appengine/docs/python/channel/javascript
 *
 * Requires a hosted HTML page that opens the desired channel and dispatches JS
 * on{Open,Message,Close,Error}() events to a global object named
 * "androidMessageHandler".
 */
public class GAEChannelClient {
  private static final String TAG = "GAEChannelClient";
  private WebView webView;
  private final ProxyingMessageHandler proxyingMessageHandler;

  /**
   * Callback interface for messages delivered on the Google AppEngine channel.
   *
   * Methods are guaranteed to be invoked on the UI thread of |activity| passed
   * to GAEChannelClient's constructor.
   */
  public interface MessageHandler {
    public void onOpen();
    public void onMessage(String data);
    public void onClose();
    public void onError(int code, String description);
  }

  /** Asynchronously open an AppEngine channel. */
  @SuppressLint("SetJavaScriptEnabled")
  public GAEChannelClient(
      Activity activity, String token, MessageHandler handler) {
    webView = new WebView(activity);
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebChromeClient(new WebChromeClient() {  // Purely for debugging.
        public boolean onConsoleMessage (ConsoleMessage msg) {
          Log.d(TAG, "console: " + msg.message() + " at " +
              msg.sourceId() + ":" + msg.lineNumber());
          return false;
        }
      });
    webView.setWebViewClient(new WebViewClient() {  // Purely for debugging.
        public void onReceivedError(
            WebView view, int errorCode, String description,
            String failingUrl) {
          Log.e(TAG, "JS error: " + errorCode + " in " + failingUrl +
              ", desc: " + description);
        }
      });
    proxyingMessageHandler = new ProxyingMessageHandler(activity, handler);
    webView.addJavascriptInterface(
        proxyingMessageHandler, "androidMessageHandler");
    webView.loadUrl("file:///android_asset/channel.html?token=" + token);
  }

  /** Close the connection to the AppEngine channel. */
  public void close() {
    if (webView == null) {
      return;
    }
    proxyingMessageHandler.disconnect();
    webView.removeJavascriptInterface("androidMessageHandler");
    webView.loadUrl("about:blank");
    webView = null;
  }

  // Helper class for proxying callbacks from the Java<->JS interaction
  // (private, background) thread to the Activity's UI thread.
  private static class ProxyingMessageHandler {
    private final Activity activity;
    private final MessageHandler handler;
    private final boolean[] disconnected = { false };

    public ProxyingMessageHandler(Activity activity, MessageHandler handler) {
      this.activity = activity;
      this.handler = handler;
    }

    public void disconnect() {
      disconnected[0] = true;
    }

    private boolean disconnected() {
      return disconnected[0];
    }

    @JavascriptInterface public void onOpen() {
      activity.runOnUiThread(new Runnable() {
          public void run() {
            if (!disconnected()) {
              handler.onOpen();
            }
          }
        });
    }

    @JavascriptInterface public void onMessage(final String data) {
      activity.runOnUiThread(new Runnable() {
          public void run() {
            if (!disconnected()) {
              handler.onMessage(data);
            }
          }
        });
    }

    @JavascriptInterface public void onClose() {
      activity.runOnUiThread(new Runnable() {
          public void run() {
            if (!disconnected()) {
              handler.onClose();
            }
          }
        });
    }

    @JavascriptInterface public void onError(
        final int code, final String description) {
      activity.runOnUiThread(new Runnable() {
          public void run() {
            if (!disconnected()) {
              handler.onError(code, description);
            }
          }
        });
    }
  }
}
