/*
 * Copyright (C) 2008 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.
 */
/*
 * JDWP initialization.
 */
#include "jdwp/JdwpPriv.h"
#include "Dalvik.h"
#include "Atomic.h"

#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <errno.h>


static void* jdwpThreadStart(void* arg);


/*
 * Initialize JDWP.
 *
 * Does not return until JDWP thread is running, but may return before
 * the thread is accepting network connections.
 */
JdwpState* dvmJdwpStartup(const JdwpStartupParams* pParams)
{
    JdwpState* state = NULL;
    int i, sleepIter;
    u8 startWhen;

    /* comment this out when debugging JDWP itself */
    android_setMinPriority(LOG_TAG, ANDROID_LOG_DEBUG);

    state = (JdwpState*) calloc(1, sizeof(JdwpState));

    state->params = *pParams;

    state->requestSerial = 0x10000000;
    state->eventSerial = 0x20000000;
    dvmDbgInitMutex(&state->threadStartLock);
    dvmDbgInitMutex(&state->attachLock);
    dvmDbgInitMutex(&state->serialLock);
    dvmDbgInitMutex(&state->eventLock);
    state->eventThreadId = 0;
    dvmDbgInitMutex(&state->eventThreadLock);
    dvmDbgInitCond(&state->threadStartCond);
    dvmDbgInitCond(&state->attachCond);
    dvmDbgInitCond(&state->eventThreadCond);

    switch (pParams->transport) {
    case kJdwpTransportSocket:
        // LOGD("prepping for JDWP over TCP\n");
        state->transport = dvmJdwpSocketTransport();
        break;
    case kJdwpTransportAndroidAdb:
        // LOGD("prepping for JDWP over ADB\n");
        state->transport = dvmJdwpAndroidAdbTransport();
        /* TODO */
        break;
    default:
        LOGE("Unknown transport %d\n", pParams->transport);
        assert(false);
        goto fail;
    }

    if (!dvmJdwpNetStartup(state, pParams))
        goto fail;

    /*
     * Grab a mutex or two before starting the thread.  This ensures they
     * won't signal the cond var before we're waiting.
     */
    dvmDbgLockMutex(&state->threadStartLock);
    if (pParams->suspend)
        dvmDbgLockMutex(&state->attachLock);

    /*
     * We have bound to a port, or are trying to connect outbound to a
     * debugger.  Create the JDWP thread and let it continue the mission.
     */
    if (!dvmCreateInternalThread(&state->debugThreadHandle, "JDWP",
            jdwpThreadStart, state))
    {
        /* state is getting tossed, but unlock these anyway for cleanliness */
        dvmDbgUnlockMutex(&state->threadStartLock);
        if (pParams->suspend)
            dvmDbgUnlockMutex(&state->attachLock);
        goto fail;
    }

    /*
     * Wait until the thread finishes basic initialization.
     * TODO: cond vars should be waited upon in a loop
     */
    dvmDbgCondWait(&state->threadStartCond, &state->threadStartLock);
    dvmDbgUnlockMutex(&state->threadStartLock);


    /*
     * For suspend=y, wait for the debugger to connect to us or for us to
     * connect to the debugger.
     *
     * The JDWP thread will signal us when it connects successfully or
     * times out (for timeout=xxx), so we have to check to see what happened
     * when we wake up.
     */
    if (pParams->suspend) {
        dvmChangeStatus(NULL, THREAD_VMWAIT);
        dvmDbgCondWait(&state->attachCond, &state->attachLock);
        dvmDbgUnlockMutex(&state->attachLock);
        dvmChangeStatus(NULL, THREAD_RUNNING);

        if (!dvmJdwpIsActive(state)) {
            LOGE("JDWP connection failed\n");
            goto fail;
        }

        LOGI("JDWP connected\n");

        /*
         * Ordinarily we would pause briefly to allow the debugger to set
         * breakpoints and so on, but for "suspend=y" the VM init code will
         * pause the VM when it sends the VM_START message.
         */
    }

    return state;

fail:
    dvmJdwpShutdown(state);     // frees state
    return NULL;
}

/*
 * Reset all session-related state.  There should not be an active connection
 * to the client at this point (we may be listening for a new one though).
 *
 * This includes freeing up the debugger event list.
 */
void dvmJdwpResetState(JdwpState* state)
{
    /* could reset the serial numbers, but no need to */

    dvmJdwpUnregisterAll(state);
    assert(state->eventList == NULL);

    /*
     * Should not have one of these in progress.  If the debugger went away
     * mid-request, though, we could see this.
     */
    if (state->eventThreadId != 0) {
        LOGW("WARNING: resetting state while event in progress\n");
        assert(false);
    }
}

/*
 * Tell the JDWP thread to shut down.  Frees "state".
 */
void dvmJdwpShutdown(JdwpState* state)
{
    void* threadReturn;

    if (state == NULL)
        return;

    if (dvmJdwpIsTransportDefined(state)) {
        if (dvmJdwpIsConnected(state))
            dvmJdwpPostVMDeath(state);

        /*
         * Close down the network to inspire the thread to halt.
         */
        LOGD("JDWP shutting down net...\n");
        dvmJdwpNetShutdown(state);

        if (state->debugThreadStarted) {
            state->run = false;
            if (pthread_join(state->debugThreadHandle, &threadReturn) != 0) {
                LOGW("JDWP thread join failed\n");
            }
        }

        LOGV("JDWP freeing netstate...\n");
        dvmJdwpNetFree(state);
        state->netState = NULL;
    }
    assert(state->netState == NULL);

    dvmJdwpResetState(state);
    free(state);
}

/*
 * Are we talking to a debugger?
 */ 
bool dvmJdwpIsActive(JdwpState* state)
{
    return dvmJdwpIsConnected(state);
}

/*
 * Entry point for JDWP thread.  The thread was created through the VM
 * mechanisms, so there is a java/lang/Thread associated with us.
 */
static void* jdwpThreadStart(void* arg)
{
    JdwpState* state = (JdwpState*) arg;

    LOGV("JDWP: thread running\n");

    /*
     * Finish initializing "state", then notify the creating thread that
     * we're running.
     */
    state->debugThreadHandle = dvmThreadSelf()->handle;
    state->run = true;
    MEM_BARRIER();
    state->debugThreadStarted = true;       // touch this last

    dvmDbgLockMutex(&state->threadStartLock);
    dvmDbgCondBroadcast(&state->threadStartCond);
    dvmDbgUnlockMutex(&state->threadStartLock);

    /* set the thread state to VMWAIT so GCs don't wait for us */
    dvmDbgThreadWaiting();

    /*
     * Loop forever if we're in server mode, processing connections.  In
     * non-server mode, we bail out of the thread when the debugger drops
     * us.
     *
     * We broadcast a notification when a debugger attaches, after we
     * successfully process the handshake.
     */
    while (state->run) {
        bool first;
        int cc;

        if (state->params.server) {
            /*
             * Block forever, waiting for a connection.  To support the
             * "timeout=xxx" option we'll need to tweak this.
             */
            if (!dvmJdwpAcceptConnection(state))
                break;
        } else {
            /*
             * If we're not acting as a server, we need to connect out to the
             * debugger.  To support the "timeout=xxx" option we need to
             * have a timeout if the handshake reply isn't received in a
             * reasonable amount of time.
             */
            if (!dvmJdwpEstablishConnection(state)) {
                /* wake anybody who was waiting for us to succeed */
                dvmDbgLockMutex(&state->attachLock);
                dvmDbgCondBroadcast(&state->attachCond);
                dvmDbgUnlockMutex(&state->attachLock);
                break;
            }
        }

        /* prep debug code to handle the new connection */
        dvmDbgConnected();

        /* process requests until the debugger drops */
        first = true;
        while (true) {
            // sanity check -- shouldn't happen?
            if (dvmThreadSelf()->status != THREAD_VMWAIT) {
                LOGE("JDWP thread no longer in VMWAIT (now %d); resetting\n",
                    dvmThreadSelf()->status);
                dvmDbgThreadWaiting();
            }

            if (!dvmJdwpProcessIncoming(state))     /* blocking read */
                break;

            if (first && !dvmJdwpAwaitingHandshake(state)) {
                /* handshake worked, tell the interpreter that we're active */
                first = false;

                /* set thread ID; requires object registry to be active */
                state->debugThreadId = dvmDbgGetThreadSelfId();

                /* wake anybody who's waiting for us */
                dvmDbgLockMutex(&state->attachLock);
                dvmDbgCondBroadcast(&state->attachCond);
                dvmDbgUnlockMutex(&state->attachLock);
            }
        }

        dvmJdwpCloseConnection(state);

        if (state->ddmActive) {
            state->ddmActive = false;

            /* broadcast the disconnect; must be in RUNNING state */
            dvmDbgThreadRunning();
            dvmDbgDdmDisconnected();
            dvmDbgThreadWaiting();
        }

        /* interpreter can ignore breakpoints */
        dvmDbgDisconnected();

        /* if we had stuff suspended, resume it now */
        dvmUndoDebuggerSuspensions();

        dvmJdwpResetState(state);

        /* if we connected out, this was a one-shot deal */
        if (!state->params.server)
            state->run = false;
    }

    /* back to running, for thread shutdown */
    dvmDbgThreadRunning();

    LOGV("JDWP: thread exiting\n");
    return NULL;
}


/*
 * Return the thread handle, or (pthread_t)0 if the debugger isn't running.
 */
pthread_t dvmJdwpGetDebugThread(JdwpState* state)
{
    if (state == NULL)
        return 0;

    return state->debugThreadHandle;
}

#if 0
/*
 * Wait until the debugger attaches.  Returns immediately if the debugger
 * is already attached.
 *
 * If we return the instant the debugger connects, we run the risk of
 * executing code before the debugger has had a chance to configure
 * breakpoints or issue suspend calls.  It would be nice to just sit in
 * the suspended state, but most debuggers don't expect any threads to be
 * suspended when they attach.
 *
 * There's no event we can post to tell the debugger "we've stopped, and
 * we like it that way".  We could send a fake breakpoint, which should
 * cause the debugger to immediately send a resume, but the debugger might
 * send the resume immediately or might throw an exception of its own upon
 * receiving a breakpoint event that it didn't ask for.
 *
 * What we really want is a "wait until the debugger is done configuring
 * stuff" event.  We can get close with a "wait until the debugger has
 * been idle for a brief period", and we can do a mild approximation with
 * "just sleep for a second after it connects".
 *
 * We should be in THREAD_VMWAIT here, so we're not allowed to do anything
 * with objects because a GC could be in progress.
 *
 * NOTE: this trips as soon as something connects to the socket.  This
 * is no longer appropriate -- we don't want to return when DDMS connects.
 * We could fix this by polling for the first debugger packet, but we have
 * to watch out for disconnects.  If we're going to do polling, it's
 * probably best to do it at a higher level.
 */
void dvmJdwpWaitForDebugger(JdwpState* state)
{
    // no more
}
#endif

/*
 * Get a notion of the current time, in milliseconds.  We leave it in
 * two 32-bit pieces.
 */
void dvmJdwpGetNowMsec(long* pSec, long* pMsec)
{
#ifdef HAVE_POSIX_CLOCKS
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    *pSec = now.tv_sec;
    *pMsec = now.tv_nsec / 1000000;
#else
    struct timeval now;
    gettimeofday(&now, NULL);
    *pSec = now.tv_sec;
    *pMsec = now.tv_usec / 1000;
#endif
}

/*
 * Return the time, in milliseconds, since the last debugger activity.
 *
 * Returns -1 if no debugger is attached, or 0 if we're in the middle of
 * processing a debugger request.
 */
s8 dvmJdwpLastDebuggerActivity(JdwpState* state)
{
    long lastSec, lastMsec;
    long nowSec, nowMsec;

    /* these are volatile; lastSec becomes 0 during update */
    lastSec = state->lastActivitySec;
    lastMsec = state->lastActivityMsec;

    /* initializing or in the middle of something? */
    if (lastSec == 0 || state->lastActivitySec != lastSec) {
        //LOGI("+++ last=busy\n");
        return 0;
    }

    /* get the current time *after* latching the "last" time */
    dvmJdwpGetNowMsec(&nowSec, &nowMsec);

    s8 last = (s8)lastSec * 1000 + lastMsec;
    s8 now = (s8)nowSec * 1000 + nowMsec;

    //LOGI("last is %ld.%ld --> %lld\n", lastSec, lastMsec, last);
    //LOGI("now is  %ld.%ld --> %lld\n", nowSec, nowMsec, now);


    //LOGI("+++ interval=%lld\n", now - last);
    return now - last;
}