// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.SparseArray;
import android.webkit.ValueCallback;

import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;

/**
 * Provides the Message Channel functionality for Android Webview. Specifically
 * manages the message ports that are associated with a message channel and
 * handles posting/receiving messages to/from them.
 * See https://html.spec.whatwg.org/multipage/comms.html#messagechannel for
 * further information on message channels.
 *
 * The message ports have unique IDs. In Android webview implementation,
 * the message ports are only known by their IDs at the native side.
 * At the java side, the embedder deals with MessagePort objects. The mapping
 * from an ID to an object is in AwMessagePortService. AwMessagePortService
 * keeps a strong ref to MessagePort objects until they are closed.
 *
 * Ownership: The Java AwMessagePortService is owned by Java AwBrowserContext.
 * The native AwMessagePortService is owned by native AwBrowserContext. The
 * native peer maintains a weak ref to the java object and deregisters itself
 * before being deleted.
 *
 * All methods are called on UI thread except as noted.
 */
@JNINamespace("android_webview")
public class AwMessagePortService {

    private static final String TAG = "AwMessagePortService";

    private static final int POST_MESSAGE = 1;

    // TODO(sgurun) implement transferring ports from JS to Java using message channels.
    private static class PostMessage {
        public MessagePort port;
        public String message;

        public PostMessage(MessagePort port, String message) {
            this.port = port;
            this.message = message;
        }
    }

    // The messages from JS to Java are posted to a message port on a background thread.
    // We do this to make any potential synchronization between Java and JS
    // easier for user programs.
    private static class MessageHandler extends Thread {
        private Handler mHandler;

        public Handler getHandler() {
            return mHandler;
        }

        public void run() {
            Looper.prepare();
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    if (msg.what == POST_MESSAGE) {
                        PostMessage m = (PostMessage) msg.obj;
                        m.port.onMessage(m.message);
                        return;
                    }
                    throw new IllegalStateException("undefined message");
                }
            };
            Looper.loop();
        }
    }

    // A thread safe storage for Message Ports.
    private static class MessagePortStorage {
        private SparseArray<MessagePort> mMessagePorts = new SparseArray<MessagePort>();
        private Object mLock = new Object();

        public void put(int portId, MessagePort m) {
            synchronized (mLock) {
                mMessagePorts.put(portId, m);
            }
        }
        public MessagePort get(int portId) {
            synchronized (mLock) {
                return mMessagePorts.get(portId);
            }
        }
    }

    private long mNativeMessagePortService;
    private MessagePortStorage mPortStorage = new MessagePortStorage();
    private MessageHandler mMessageHandler = new MessageHandler();

    AwMessagePortService() {
        mNativeMessagePortService = nativeInitAwMessagePortService();
        mMessageHandler.start();
    }

    private MessagePort addPort(int portId) {
        if (mPortStorage.get(portId) != null) {
            throw new IllegalStateException("Port already exists");
        }
        MessagePort m = new MessagePort(portId);
        mPortStorage.put(portId, m);
        return m;
    }

    @CalledByNative
    private void onMessageChannelCreated(int portId1, int portId2,
            ValueCallback<MessageChannel> callback) {
        callback.onReceiveValue(new MessageChannel(addPort(portId1), addPort(portId2)));
    }

    // Called on IO thread.
    @CalledByNative
    private void onPostMessage(int portId, String message, int[] ports) {
        PostMessage m = new PostMessage(mPortStorage.get(portId), message);
        Handler handler = mMessageHandler.getHandler();
        Message msg = handler.obtainMessage(POST_MESSAGE, m);
        handler.sendMessage(msg);
    }

    @CalledByNative
    private void unregisterNativeAwMessagePortService() {
        mNativeMessagePortService = 0;
    }

    private native long nativeInitAwMessagePortService();
}

@@ -2210,16 +2210,22 @@
 eHandler
+Thread
  extends
@@ -2254,206 +2254,76 @@
 ate 
-Handler mHandler;%0A%0A        public Handler 
+static class Messa
 ge
-t
 Handler
-() %7B%0A            return mHandler;%0A        %7D%0A%0A        public void run() %7B%0A            Looper.prepare();%0A            mHandler = new Handler() %7B%0A    
+ extends Handler %7B%0A            @Override%0A
     
@@ -2383,28 +2383,24 @@
             
-    
 if (msg.what
@@ -2435,28 +2435,24 @@
             
-  
-  
 PostMessage 
@@ -2478,20 +2478,16 @@
 sg.obj;%0A
-    
         
@@ -2543,28 +2543,24 @@
             
-    
 return;%0A    
@@ -2567,34 +2567,26 @@
             
-   
- 
 %7D%0A
-    
             
@@ -2659,27 +2659,242 @@
     
-    %7D%0A            %7D
+%7D%0A        %7D%0A%0A        private MessageHandler mHandler;%0A%0A        public Handler getHandler() %7B%0A            return mHandler;%0A        %7D%0A%0A        public void run() %7B%0A            Looper.prepare();%0A            mHandler = new MessageHandler()
 ;%0A  
@@ -3625,16 +3625,22 @@
 eHandler
+Thread
  mMessag
@@ -3643,24 +3643,30 @@
 ssageHandler
+Thread
  = new Messa
@@ -3670,24 +3670,30 @@
 ssageHandler
+Thread
 ();%0A%0A    AwM
@@ -3807,16 +3807,22 @@
 eHandler
+Thread
 .start()
@@ -4589,16 +4589,22 @@
 eHandler
+Thread
 .getHand

Address a warning about non-static classes.