/*
 * 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 com.android.ddmlib;

import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * Handle thread status updates.
 */
final class HandleThread extends ChunkHandler {

    public static final int CHUNK_THEN = type("THEN");
    public static final int CHUNK_THCR = type("THCR");
    public static final int CHUNK_THDE = type("THDE");
    public static final int CHUNK_THST = type("THST");
    public static final int CHUNK_THNM = type("THNM");
    public static final int CHUNK_STKL = type("STKL");

    private static final HandleThread mInst = new HandleThread();

    // only read/written by requestThreadUpdates()
    private static volatile boolean sThreadStatusReqRunning = false;
    private static volatile boolean sThreadStackTraceReqRunning = false;

    private HandleThread() {}


    /**
     * Register for the packets we expect to get from the client.
     */
    public static void register(MonitorThread mt) {
        mt.registerChunkHandler(CHUNK_THCR, mInst);
        mt.registerChunkHandler(CHUNK_THDE, mInst);
        mt.registerChunkHandler(CHUNK_THST, mInst);
        mt.registerChunkHandler(CHUNK_THNM, mInst);
        mt.registerChunkHandler(CHUNK_STKL, mInst);
    }

    /**
     * Client is ready.
     */
    @Override
    public void clientReady(Client client) throws IOException {
        Log.d("ddm-thread", "Now ready: " + client);
        if (client.isThreadUpdateEnabled())
            sendTHEN(client, true);
    }

    /**
     * Client went away.
     */
    @Override
    public void clientDisconnected(Client client) {}

    /**
     * Chunk handler entry point.
     */
    @Override
    public void handleChunk(Client client, int type, ByteBuffer data, boolean isReply, int msgId) {

        Log.d("ddm-thread", "handling " + ChunkHandler.name(type));

        if (type == CHUNK_THCR) {
            handleTHCR(client, data);
        } else if (type == CHUNK_THDE) {
            handleTHDE(client, data);
        } else if (type == CHUNK_THST) {
            handleTHST(client, data);
        } else if (type == CHUNK_THNM) {
            handleTHNM(client, data);
        } else if (type == CHUNK_STKL) {
            handleSTKL(client, data);
        } else {
            handleUnknownChunk(client, type, data, isReply, msgId);
        }
    }

    /*
     * Handle a thread creation message.
     *
     * We should be tolerant of receiving a duplicate create message.  (It
     * shouldn't happen with the current implementation.)
     */
    private void handleTHCR(Client client, ByteBuffer data) {
        int threadId, nameLen;
        String name;

        threadId = data.getInt();
        nameLen = data.getInt();
        name = ByteBufferUtil.getString(data, nameLen);

        Log.v("ddm-thread", "THCR: " + threadId + " '" + name + "'");

        client.getClientData().addThread(threadId, name);
        client.update(Client.CHANGE_THREAD_DATA);
    }

    /*
     * Handle a thread death message.
     */
    private void handleTHDE(Client client, ByteBuffer data) {
        int threadId;

        threadId = data.getInt();
        Log.v("ddm-thread", "THDE: " + threadId);

        client.getClientData().removeThread(threadId);
        client.update(Client.CHANGE_THREAD_DATA);
    }

    /*
     * Handle a thread status update message.
     *
     * Response has:
     *  (1b) header len
     *  (1b) bytes per entry
     *  (2b) thread count
     * Then, for each thread:
     *  (4b) threadId (matches value from THCR)
     *  (1b) thread status
     *  (4b) tid
     *  (4b) utime
     *  (4b) stime
     */
    private void handleTHST(Client client, ByteBuffer data) {
        int headerLen, bytesPerEntry, extraPerEntry;
        int threadCount;

        headerLen = (data.get() & 0xff);
        bytesPerEntry = (data.get() & 0xff);
        threadCount = data.getShort();

        headerLen -= 4;     // we've read 4 bytes
        while (headerLen-- > 0)
            data.get();

        extraPerEntry = bytesPerEntry - 18;     // we want 18 bytes

        Log.v("ddm-thread", "THST: threadCount=" + threadCount);

        /*
         * For each thread, extract the data, find the appropriate
         * client, and add it to the ClientData.
         */
        for (int i = 0; i < threadCount; i++) {
            int threadId, status, tid, utime, stime;
            boolean isDaemon = false;

            threadId = data.getInt();
            status = data.get();
            tid = data.getInt();
            utime = data.getInt();
            stime = data.getInt();
            if (bytesPerEntry >= 18)
                isDaemon = (data.get() != 0);

            Log.v("ddm-thread", "  id=" + threadId
                + ", status=" + status + ", tid=" + tid
                + ", utime=" + utime + ", stime=" + stime);

            ClientData cd = client.getClientData();
            ThreadInfo threadInfo = cd.getThread(threadId);
            if (threadInfo != null)
                threadInfo.updateThread(status, tid, utime, stime, isDaemon);
            else
                Log.d("ddms", "Thread with id=" + threadId + " not found");

            // slurp up any extra
            for (int slurp = extraPerEntry; slurp > 0; slurp--)
                data.get();
        }

        client.update(Client.CHANGE_THREAD_DATA);
    }

    /*
     * Handle a THNM (THread NaMe) message.  We get one of these after
     * somebody calls Thread.setName() on a running thread.
     */
    private void handleTHNM(Client client, ByteBuffer data) {
        int threadId, nameLen;
        String name;

        threadId = data.getInt();
        nameLen = data.getInt();
        name = ByteBufferUtil.getString(data, nameLen);

        Log.v("ddm-thread", "THNM: " + threadId + " '" + name + "'");

        ThreadInfo threadInfo = client.getClientData().getThread(threadId);
        if (threadInfo != null) {
            threadInfo.setThreadName(name);
            client.update(Client.CHANGE_THREAD_DATA);
        } else {
            Log.d("ddms", "Thread with id=" + threadId + " not found");
        }
    }


    /**
     * Parse an incoming STKL.
     */
    private void handleSTKL(Client client, ByteBuffer data) {
        StackTraceElement[] trace;
        int i, threadId, stackDepth;
        @SuppressWarnings("unused")
        int future;

        future = data.getInt();
        threadId = data.getInt();

        Log.v("ddms", "STKL: " + threadId);

        /* un-serialize the StackTraceElement[] */
        stackDepth = data.getInt();
        trace = new StackTraceElement[stackDepth];
        for (i = 0; i < stackDepth; i++) {
            String className, methodName, fileName;
            int len, lineNumber;

            len = data.getInt();
            className = ByteBufferUtil.getString(data, len);
            len = data.getInt();
            methodName = ByteBufferUtil.getString(data, len);
            len = data.getInt();
            if (len == 0) {
                fileName = null;
            } else {
                fileName = ByteBufferUtil.getString(data, len);
            }
            lineNumber = data.getInt();

            trace[i] = new StackTraceElement(className, methodName, fileName,
                        lineNumber);
        }

        ThreadInfo threadInfo = client.getClientData().getThread(threadId);
        if (threadInfo != null) {
            threadInfo.setStackCall(trace);
            client.update(Client.CHANGE_THREAD_STACKTRACE);
        } else {
            Log.d("STKL", String.format(
                    "Got stackcall for thread %1$d, which does not exists (anymore?).", //$NON-NLS-1$
                    threadId));
        }
    }


    /**
     * Send a THEN (THread notification ENable) request to the client.
     */
    public static void sendTHEN(Client client, boolean enable)
        throws IOException {

        ByteBuffer rawBuf = allocBuffer(1);
        JdwpPacket packet = new JdwpPacket(rawBuf);
        ByteBuffer buf = getChunkDataBuf(rawBuf);

        if (enable)
            buf.put((byte)1);
        else
            buf.put((byte)0);

        finishChunkPacket(packet, CHUNK_THEN, buf.position());
        Log.d("ddm-thread", "Sending " + name(CHUNK_THEN) + ": " + enable);
        client.send(packet, mInst);
    }


    /**
     * Send a STKL (STacK List) request to the client.  The VM will suspend
     * the target thread, obtain its stack, and return it.  If the thread
     * is no longer running, a failure result will be returned.
     */
    public static void sendSTKL(Client client, int threadId)
        throws IOException {

        if (false) {
            Log.d("ddm-thread", "would send STKL " + threadId);
            return;
        }

        ByteBuffer rawBuf = allocBuffer(4);
        JdwpPacket packet = new JdwpPacket(rawBuf);
        ByteBuffer buf = getChunkDataBuf(rawBuf);

        buf.putInt(threadId);

        finishChunkPacket(packet, CHUNK_STKL, buf.position());
        Log.d("ddm-thread", "Sending " + name(CHUNK_STKL) + ": " + threadId);
        client.send(packet, mInst);
    }


    /**
     * This is called periodically from the UI thread.  To avoid locking
     * the UI while we request the updates, we create a new thread.
     *
     */
    static void requestThreadUpdate(final Client client) {
        if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
            if (sThreadStatusReqRunning) {
                Log.w("ddms", "Waiting for previous thread update req to finish");
                return;
            }

            new Thread("Thread Status Req") {
                @Override
                public void run() {
                    sThreadStatusReqRunning = true;
                    try {
                        sendTHST(client);
                    } catch (IOException ioe) {
                        Log.d("ddms", "Unable to request thread updates from "
                                + client + ": " + ioe.getMessage());
                    } finally {
                        sThreadStatusReqRunning = false;
                    }
                }
            }.start();
        }
    }

    static void requestThreadStackCallRefresh(final Client client, final int threadId) {
        if (client.isDdmAware() && client.isThreadUpdateEnabled()) {
            if (sThreadStackTraceReqRunning) {
                Log.w("ddms", "Waiting for previous thread stack call req to finish");
                return;
            }

            new Thread("Thread Status Req") {
                @Override
                public void run() {
                    sThreadStackTraceReqRunning = true;
                    try {
                        sendSTKL(client, threadId);
                    } catch (IOException ioe) {
                        Log.d("ddms", "Unable to request thread stack call updates from "
                                + client + ": " + ioe.getMessage());
                    } finally {
                        sThreadStackTraceReqRunning = false;
                    }
                }
            }.start();
        }

    }

    /*
     * Send a THST request to the specified client.
     */
    private static void sendTHST(Client client) throws IOException {
        ByteBuffer rawBuf = allocBuffer(0);
        JdwpPacket packet = new JdwpPacket(rawBuf);
        ByteBuffer buf = getChunkDataBuf(rawBuf);

        // nothing much to say

        finishChunkPacket(packet, CHUNK_THST, buf.position());
        Log.d("ddm-thread", "Sending " + name(CHUNK_THST));
        client.send(packet, mInst);
    }
}

