/**
 * Copyright (C) 2017 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.
 */

#define LOG_TAG "NativeCallbackThread"
//#define LOG_NDEBUG 0

#include "NativeCallbackThread.h"

#include <utils/Log.h>

namespace android {

using std::lock_guard;
using std::mutex;
using std::unique_lock;

NativeCallbackThread::NativeCallbackThread(JavaVM *vm) : mvm(vm), mExiting(false),
        mThread(&NativeCallbackThread::threadLoop, this) {
    ALOGD("Started native callback thread %p", this);
}

NativeCallbackThread::~NativeCallbackThread() {
    ALOGV("%s %p", __func__, this);
    stop();
}

void NativeCallbackThread::threadLoop() {
    ALOGV("%s", __func__);

    JNIEnv *env = nullptr;
    JavaVMAttachArgs aargs = {JNI_VERSION_1_4, "NativeCallbackThread", nullptr};
    if (mvm->AttachCurrentThread(&env, &aargs) != JNI_OK || env == nullptr) {
        ALOGE("Couldn't attach thread");
        mExiting = true;
        return;
    }

    while (true) {
        Task task;
        {
            unique_lock<mutex> lk(mQueueMutex);

            if (mExiting) break;
            if (mQueue.empty()) {
                ALOGV("Waiting for task...");
                mQueueCond.wait(lk);
                if (mExiting) break;
                if (mQueue.empty()) continue;
            }

            task = mQueue.front();
            mQueue.pop();
        }

        ALOGV("Executing task...");
        task(env);
        if (env->ExceptionCheck()) {
            ALOGE("Unexpected exception:");
            env->ExceptionDescribe();
            env->ExceptionClear();
        }
    }

    auto res = mvm->DetachCurrentThread();
    ALOGE_IF(res != JNI_OK, "Couldn't detach thread");

    ALOGV("Native callback thread %p finished", this);
    ALOGD_IF(!mQueue.empty(), "Skipped execution of %zu tasks", mQueue.size());
}

void NativeCallbackThread::enqueue(const Task &task) {
    lock_guard<mutex> lk(mQueueMutex);

    if (mExiting) {
        ALOGW("Callback thread %p is not serving calls", this);
        return;
    }

    ALOGV("Adding task to the queue...");
    mQueue.push(task);
    mQueueCond.notify_one();
}

void NativeCallbackThread::stop() {
    ALOGV("%s %p", __func__, this);

    {
        lock_guard<mutex> lk(mQueueMutex);

        if (mExiting) return;

        mExiting = true;
        mQueueCond.notify_one();
    }

    if (mThread.get_id() == std::this_thread::get_id()) {
        // you can't self-join a thread, but it's ok when calling from our sub-task
        ALOGD("About to stop native callback thread %p", this);
        mThread.detach();
    } else {
        mThread.join();
        ALOGD("Stopped native callback thread %p", this);
    }
}

} // namespace android
