/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#include "Thread.h"

#ifdef _WIN32
#pragma warning( disable: 4311 )
#endif

#include <decaf/internal/DecafRuntime.h>
#include <decaf/internal/util/concurrent/Threading.h>
#include <decaf/lang/Integer.h>
#include <decaf/lang/Long.h>
#include <decaf/lang/Math.h>
#include <decaf/lang/Exception.h>
#include <decaf/lang/exceptions/RuntimeException.h>
#include <decaf/lang/exceptions/NullPointerException.h>
#include <decaf/lang/exceptions/IllegalThreadStateException.h>
#include <decaf/util/concurrent/TimeUnit.h>
#include <decaf/util/concurrent/Mutex.h>
#include <decaf/util/concurrent/Executors.h>

#include <vector>

using namespace decaf;
using namespace decaf::internal;
using namespace decaf::internal::util;
using namespace decaf::internal::util::concurrent;
using namespace decaf::lang;
using namespace decaf::lang::exceptions;
using namespace decaf::util;
using namespace decaf::util::concurrent;

////////////////////////////////////////////////////////////////////////////////
namespace decaf {
namespace lang {

    class ThreadProperties {
    private:

        ThreadProperties( const ThreadProperties& );
        ThreadProperties& operator= ( const ThreadProperties& );

    public:

        Runnable* task;
        ThreadHandle* handle;
        Thread::UncaughtExceptionHandler* exHandler;
        static unsigned int id;
        static Thread::UncaughtExceptionHandler* defaultHandler;

    public:

        ThreadProperties() : task(NULL), handle(NULL), exHandler(NULL) {}

    };

}}

////////////////////////////////////////////////////////////////////////////////
unsigned int ThreadProperties::id = 0;
Thread::UncaughtExceptionHandler* ThreadProperties::defaultHandler = NULL;

////////////////////////////////////////////////////////////////////////////////
Thread::Thread(ThreadHandle* handle) : Runnable(), properties(NULL) {

    this->properties = new ThreadProperties();
    this->properties->handle = handle;
}

////////////////////////////////////////////////////////////////////////////////
Thread::Thread() : Runnable(), properties(NULL) {
    this->initializeSelf(NULL, "", -1);
}

////////////////////////////////////////////////////////////////////////////////
Thread::Thread(Runnable* task) : Runnable(), properties(NULL) {
    this->initializeSelf(task, "", -1);
}

////////////////////////////////////////////////////////////////////////////////
Thread::Thread(const std::string& name) : Runnable(), properties(NULL) {
    this->initializeSelf(NULL, name, -1);
}

////////////////////////////////////////////////////////////////////////////////
Thread::Thread(Runnable* task, const std::string& name) : Runnable(), properties( NULL ) {
    this->initializeSelf(task, name, -1);
}

////////////////////////////////////////////////////////////////////////////////
Thread::Thread(Runnable* task, const std::string& name, long long stackSize) : Runnable(), properties( NULL ) {
    this->initializeSelf(task, name, stackSize);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::initializeSelf(Runnable* task, const std::string& name, long long stackSize) {

    std::string threadName = name;

    if (threadName.empty()) {
        threadName = std::string("Thread-") + Integer::toString(++ThreadProperties::id);
    } else {
        threadName = name;
    }

    this->properties = new ThreadProperties();
    this->properties->handle =
        Threading::createNewThread(this, threadName.c_str(), stackSize);
    this->properties->task = task;
}

////////////////////////////////////////////////////////////////////////////////
Thread::~Thread() {
    try {
        Threading::destroyThread(this->properties->handle);
        delete this->properties;
    }
    DECAF_CATCH_NOTHROW(Exception)
    DECAF_CATCHALL_NOTHROW()
}

////////////////////////////////////////////////////////////////////////////////
void Thread::run() {

    if (this->properties->task != NULL) {
        this->properties->task->run();
    }
}

////////////////////////////////////////////////////////////////////////////////
void Thread::start() {

    try {

        if (Threading::getThreadState(this->properties->handle) > Thread::NEW) {
            throw IllegalThreadStateException(
                __FILE__, __LINE__,
                "Thread::start - Thread already started");
        }

        Threading::start(this->properties->handle);
    }
    DECAF_CATCH_RETHROW(IllegalThreadStateException)
    DECAF_CATCH_RETHROW(RuntimeException)
    DECAF_CATCH_EXCEPTION_CONVERT(NullPointerException, RuntimeException)
    DECAF_CATCHALL_THROW(RuntimeException)
}

////////////////////////////////////////////////////////////////////////////////
void Thread::join() {
    Threading::join(this->properties->handle, 0, 0);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::join(long long millisecs) {

    if (millisecs < 0) {
        throw IllegalArgumentException(
            __FILE__, __LINE__,
            "Thread::join( millisecs ) - Value given {%d} is less than 0", millisecs );
    }

    Threading::join(this->properties->handle, millisecs, 0);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::join(long long millisecs, int nanos) {

    if (millisecs < 0) {
        throw IllegalArgumentException(
            __FILE__, __LINE__,
            "Thread::join( millisecs, nanos ) - Value given {%d} is less than 0", millisecs );
    }

    if (nanos < 0 || nanos > 999999) {
        throw IllegalArgumentException(
            __FILE__, __LINE__,
            "Thread::join( millisecs, nanos ) - Nanoseconds must be in range [0...999999]" );
    }

    Threading::join(this->properties->handle, millisecs, nanos);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::sleep(long long millisecs) {
    Threading::sleep(millisecs, 0);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::sleep(long long millisecs, int nanos) {

    if (millisecs < 0) {
        throw IllegalArgumentException(
            __FILE__, __LINE__,
            "Thread::sleep( millisecs, nanos ) - Value given {%d} is less than 0", millisecs );
    }

    if (nanos < 0 || nanos > 999999) {
        throw IllegalArgumentException(
            __FILE__, __LINE__,
            "Thread::sleep( millisecs, nanos ) - Nanoseconds must be in range [0...999999]" );
    }


    Threading::sleep(millisecs, nanos);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::yield() {
    Threading::yeild();
}

////////////////////////////////////////////////////////////////////////////////
long long Thread::getId() const {
    return Threading::getThreadId(this->properties->handle);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::setName(const std::string& name) {
    Threading::setThreadName(this->properties->handle, name.c_str());
}

////////////////////////////////////////////////////////////////////////////////
std::string Thread::getName() const {
    return Threading::getThreadName(this->properties->handle);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::setPriority(int value) {

    if (value < Thread::MIN_PRIORITY || value > Thread::MAX_PRIORITY) {
        throw IllegalArgumentException(
            __FILE__, __LINE__,
            "Thread::setPriority - Specified value {%d} is out of range", value );
    }

    Threading::setThreadPriority(this->properties->handle, value);
}

////////////////////////////////////////////////////////////////////////////////
int Thread::getPriority() const {
    return Threading::getThreadPriority(this->properties->handle);
}

////////////////////////////////////////////////////////////////////////////////
void Thread::setUncaughtExceptionHandler(UncaughtExceptionHandler* handler) {
    this->properties->exHandler = handler;
}

////////////////////////////////////////////////////////////////////////////////
Thread::UncaughtExceptionHandler* Thread::getUncaughtExceptionHandler() const {
    return this->properties->exHandler;
}

////////////////////////////////////////////////////////////////////////////////
Thread::UncaughtExceptionHandler* Thread::getDefaultUncaughtExceptionHandler() {
    return ThreadProperties::defaultHandler;
}

////////////////////////////////////////////////////////////////////////////////
void Thread::setDefaultUncaughtExceptionHandler(Thread::UncaughtExceptionHandler* handler) {
    ThreadProperties::defaultHandler = handler;
}

////////////////////////////////////////////////////////////////////////////////
std::string Thread::toString() const {
    return std::string(Threading::getThreadName(this->properties->handle)) + ": Priority=" +
           Integer::toString(Threading::getThreadPriority(this->properties->handle)) +
           ", ThreadID=" + Long::toString( Threading::getThreadId(this->properties->handle));
}

////////////////////////////////////////////////////////////////////////////////
bool Thread::isAlive() const {
    return Threading::isThreadAlive(this->properties->handle);
}

////////////////////////////////////////////////////////////////////////////////
Thread::State Thread::getState() const {
    return Threading::getThreadState(this->properties->handle);
}

////////////////////////////////////////////////////////////////////////////////
Thread* Thread::currentThread() {
    return Threading::getCurrentThread();
}

////////////////////////////////////////////////////////////////////////////////
void Thread::interrupt() {
    Threading::interrupt(this->properties->handle);
}

////////////////////////////////////////////////////////////////////////////////
bool Thread::interrupted() {
    return Threading::interrupted();
}

////////////////////////////////////////////////////////////////////////////////
bool Thread::isInterrupted() const {
    return Threading::isInterrupted(this->properties->handle, false);
}

////////////////////////////////////////////////////////////////////////////////
ThreadHandle* Thread::getHandle() const {
    return this->properties->handle;
}
