/*
 * Copyright (C) 2015 by Multi-Tech Systems
 *
 * This file is part of libmts.
 *
 * libmts is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * libmts is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libmts.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mts/MTS_Thread.h>
#include <mts/MTS_Logger.h>
#include <new>
#include <cassert>

using namespace MTS;

Thread::Thread(const std::string& name, bool managed)
: m_pThread(NULL)
, m_sName(name)
, m_bManaged(managed)
, m_bComplete(true)
, m_bCanceled(true)
{
    printConfig("Thread| %s()", getName().c_str());
    m_apThreadLock.reset(new Lock());
    m_apStateLock.reset(new Lock());
    m_apCompleteCondition.reset(m_apStateLock->createCondition());
}

Thread::~Thread() {
    m_bCanceled = true;
    Thread::stop();
    m_apCompleteCondition.reset();
    m_apStateLock.reset();
    m_apThreadLock.reset();
    printConfig("Thread| %s - deconstructed", getName().c_str());
}

void Thread::sleep(uint32_t millis) {
#ifdef WIN32
    Sleep(millis);
#else
    Lock lock;
    Condition* condition = lock.createCondition();
    lock.lock();
    condition->wait(millis);
    lock.unlock();
    delete condition;
#endif
}

const std::string& Thread::getName() const {
    return m_sName;
}

void Thread::start() {
    m_apThreadLock->lock();
    try {
        if (m_pThread == NULL) {
            reset();
#ifdef WIN32
            m_pThread = CreateThread(NULL, 0, &doInBackground, this, 0, NULL);
#else
            m_pThread = new (std::nothrow) pthread_t;
            if (m_pThread != NULL) {
                const int result = pthread_create(m_pThread, NULL,
                &doInBackground, this);
                assert(result == 0);
                if (result != 0) {
                    delete m_pThread;
                    m_pThread = NULL;
                }
            }

#endif
            assert(m_pThread != NULL);
            if (m_pThread != NULL) {
                printConfig("Thread| %s - started (%p)",
                getName().c_str(), m_pThread);
            } else {
                printWarning("Thread| Failed to start runnable %s",
                getName().c_str());
                m_apStateLock->lock();
                m_bCanceled = true;
                m_bComplete = true;
                m_apStateLock->unlock();
            }
        }
    } catch (...) {
        printWarning("Thread| Failed to start runnable %s", getName().c_str());
        assert(false);
    }
    m_apThreadLock->unlock();
}

void Thread::stop() {
    m_apThreadLock->lock();
    cancel();
    if (m_pThread != NULL) {
        wait(1500);

#ifdef WIN32
        if (!isDone()) {
            TerminateThread(m_pThread, 1);
            printConfig("Thread| %s - terminated", getName().c_str());
        }
        const BOOL ok = CloseHandle(m_pThread);
        assert(ok);
#else
        if (!isDone()) {
            printConfig("Thread| %s - terminated", getName().c_str());
        }
        delete m_pThread;
#endif
        m_pThread = NULL;
    } else if (!isDone()) {
        done();
    }
    m_apThreadLock->unlock();
}

void Thread::wait() {
    m_apStateLock->lock();
    if (!m_bComplete) {
        m_apCompleteCondition->wait();
    }
    m_apStateLock->unlock();
}

void Thread::wait(uint32_t millis) {
    m_apStateLock->lock();
    if (!m_bComplete) {
        m_apCompleteCondition->wait(millis);
    }
    m_apStateLock->unlock();
}

bool Thread::isAlive() const {
    bool result = false;
    m_apThreadLock->lock();
    result = (m_pThread != NULL);
    m_apThreadLock->unlock();
    return result;
}

bool Thread::isCanceled() const {
    bool result = false;
    m_apStateLock->lock();
    result = m_bCanceled;
    m_apStateLock->unlock();
    return result;
}

void Thread::cancel() {
    m_apStateLock->lock();
    if (!m_bCanceled) {
        m_bCanceled = true;
        printConfig("Thread| %s - cancelled", getName().c_str());
    }
    m_apStateLock->unlock();
}

bool Thread::isDone() const {
    bool result = false;
    m_apStateLock->lock();
    result = m_bComplete;
    m_apStateLock->unlock();
    return result;
}

void Thread::done() {
    m_apStateLock->lock();
    m_bComplete = true;
    m_apCompleteCondition->signal();
    printConfig("Thread| %s - done", getName().c_str());
    m_apStateLock->unlock();
}

void Thread::reset() {
    m_apStateLock->lock();
    m_bCanceled = false;
    m_bComplete = false;
    m_apStateLock->unlock();
}

bool Thread::isManaged() const {
    return m_bManaged;
}

#ifdef WIN32
DWORD WINAPI Thread::doInBackground(__in LPVOID parameter) {
#else
void* Thread::doInBackground(void* parameter) {
#endif

    Thread* runnable = static_cast<Thread*>(parameter);
    assert(runnable != NULL);
    if (runnable != NULL) {
        printConfig("Thread| %s - initializing\n", runnable->getName().c_str());
        try {
            runnable->run();
        } catch (...) {
            printWarning("Thread| exception caught while running %s",
            runnable->getName().c_str());
        }
        printConfig("Thread| %s - finalizing", runnable->getName().c_str());
        bool managed = runnable->isManaged();
        runnable->done();
        if (!managed) {
            printConfig("Thread| %s - deleted", runnable->getName().c_str());
            delete runnable;
        }
    }
    return 0;
}