/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.harmony.dalvik.ddmc;

import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;

import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import dalvik.annotation.optimization.FastNative;


/**
 * This represents our connection to the DDM Server.
 *
 * @hide
 */
@SystemApi(client = MODULE_LIBRARIES)
@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
public final class DdmServer {

    private static HashMap<Integer,ChunkHandler> mHandlerMap =
        new HashMap<Integer,ChunkHandler>();

    private static final int CONNECTED = 1;
    private static final int DISCONNECTED = 2;

    private static volatile boolean mRegistrationComplete = false;
    private static boolean mRegistrationTimedOut = false;


    /**
     * Don't instantiate; all members and methods are static.
     */
    private DdmServer() {}

    /**
     * Register an instance of the {@link ChunkHandler} class to handle a specific
     * chunk type.
     *
     * Throws an exception if the type already has a handler registered.
     *
     * @param type    int describing registered handler
     * @param handler handler to be registered
     * @throws NullPointerException if {@code handler} is {@code null}
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static void registerHandler(int type, ChunkHandler handler) {
        if (handler == null) {
            throw new NullPointerException("handler == null");
        }
        synchronized (mHandlerMap) {
            if (mHandlerMap.get(type) != null)
                throw new RuntimeException("type " + Integer.toHexString(type)
                    + " already registered");

            mHandlerMap.put(type, handler);
        }
    }

    /**
     * Unregister the existing handler for the specified type.
     *
     * Returns the old handler.
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static ChunkHandler unregisterHandler(int type) {
        synchronized (mHandlerMap) {
            return mHandlerMap.remove(type);
        }
    }

    /**
     * The application must call here after it finishes registering
     * handlers.
     *
     * @hide
     */
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static void registrationComplete() {
        // sync on mHandlerMap because it's convenient and makes a kind of
        // sense
        synchronized (mHandlerMap) {
            mRegistrationComplete = true;
            mHandlerMap.notifyAll();
        }
    }

    /**
     * Send a chunk of data to the DDM server.  This takes the form of a
     * JDWP "event", which does not elicit a response from the server.
     *
     * Use this for "unsolicited" chunks.
     *
     * @param chunk to send
     *
     * @hide
     */
    @UnsupportedAppUsage
    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
    public static void sendChunk(Chunk chunk) {
        nativeSendChunk(chunk.type, chunk.data, chunk.offset, chunk.length);
    }

    /* send a chunk to the DDM server */
    @FastNative
    native private static void nativeSendChunk(int type, byte[] data,
        int offset, int length);

    /*
     * Called by the VM when the DDM server connects or disconnects.
     *
     * @hide
     */
    @UnsupportedAppUsage
    private static void broadcast(int event)
    {
        synchronized (mHandlerMap) {
            Collection values = mHandlerMap.values();
            Iterator iter = values.iterator();

            while (iter.hasNext()) {
                ChunkHandler handler = (ChunkHandler) iter.next();
                switch (event) {
                    case CONNECTED:
                        handler.onConnected();
                        break;
                    case DISCONNECTED:
                        handler.onDisconnected();
                        break;
                    default:
                        throw new UnsupportedOperationException();
                }
            }
        }
    }

    /*
     * This is called by the VM when a chunk arrives.
     *
     * For a DDM-aware application, we want to wait until the app has had
     * a chance to register all of its chunk handlers.  Otherwise, we'll
     * end up dropping early-arriving packets on the floor.
     *
     * For a non-DDM-aware application, we'll end up waiting here forever
     * if DDMS happens to connect.  It's hard to know for sure that
     * registration isn't going to happen, so we settle for a timeout.
     */
    private static Chunk dispatch(int type, byte[] data, int offset, int length)
    {
        ChunkHandler handler;

        synchronized (mHandlerMap) {
            /*
             * If registration hasn't completed, and we haven't timed out
             * waiting for it, wait a bit.
             */
            while (!mRegistrationComplete && !mRegistrationTimedOut) {
                //System.out.println("dispatch() waiting for reg");
                try {
                    mHandlerMap.wait(1000);     // 1.0 sec
                } catch (InterruptedException ie) {
                    continue;
                }

                if (!mRegistrationComplete) {
                    /* timed out, don't wait again */
                    mRegistrationTimedOut = true;
                }
            }

            handler = mHandlerMap.get(type);
        }
        //System.out.println(" dispatch cont");

        if (handler == null) {
            return null;
        }

        Chunk chunk = new Chunk(type, data, offset, length);
        return handler.handleChunk(chunk);
    }
}
