/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

// -=- Threading.cxx
// Win32 Threading interface implementation

#include <malloc.h>

#include <rdr/Exception.h>
#include <rfb/LogWriter.h>
#include <rfb/util.h>
#include <rfb_win32/Threading.h>

using namespace rfb;

static LogWriter vlog("Threading");

static DWORD threadStorage = TlsAlloc();


inline void logAction(Thread* t, const char* action) {
  vlog.debug("%-16.16s %s(%lx)", action, t->getName(), t);
}

inline void logError(Thread* t, const char* err) {
  vlog.error("%-16.16s %s(%lx):%s", "failed", t->getName(), t, err);
}


DWORD WINAPI
Thread::threadProc(LPVOID lpParameter) {
  Thread* thread = (Thread*) lpParameter;
  TlsSetValue(threadStorage, thread);
  logAction(thread, "started");
  try {
    thread->run();
    logAction(thread, "stopped");
  } catch (rdr::Exception& e) {
    logError(thread, e.str());
  }
  bool deleteThread = false;
  {
    Lock l(thread->mutex);
    thread->state = ThreadStopped;
    thread->sig->signal();
    deleteThread = thread->deleteAfterRun;
  }
  if (deleteThread)
    delete thread;
  return 0;
}

Thread::Thread(const char* name_) : name(strDup(name_ ? name_ : "Unnamed")), sig(0), deleteAfterRun(false) {
  sig = new Condition(mutex);
  cond_event.h = CreateEvent(NULL, TRUE, FALSE, NULL);
  thread.h = CreateThread(NULL, 0, threadProc, this, CREATE_SUSPENDED, &thread_id);
  state = ThreadCreated;
  logAction(this, "created");
}

Thread::Thread(HANDLE thread_, DWORD thread_id_) : 
  thread(thread_), thread_id(thread_id_), name(strDup("Native")), sig(0), deleteAfterRun(false) {
  cond_event.h = CreateEvent(NULL, TRUE, FALSE, NULL);
  state = ThreadNative;
  logAction(this, "created");
}

Thread::~Thread() {
  logAction(this, "destroying");
  if (!deleteAfterRun && state != ThreadNative)
    this->join();
  if (sig)
    delete sig;
  logAction(this, "destroyed");
}

void
Thread::run() {
}

void
Thread::start() {
  Lock l(mutex);
  if (state == ThreadCreated) {
    state = ThreadStarted;
    sig->signal();
    ResumeThread(thread);
  }
}

Thread*
Thread::join() {
  if (deleteAfterRun)
    throw rdr::Exception("attempt to join() with deleteAfterRun thread");
  Lock l(mutex);
  if (state == ThreadJoined) {
    logAction(this, "already joined");
  } else {
    logAction(this, "joining");
    while (state == ThreadStarted) {
      sig->wait();
      logAction(this, "checking");
    }
    state = ThreadJoined;
    logAction(this, "joined");
  }
  return this;
}

const char*
Thread::getName() const {
  return name.buf;
}

ThreadState
Thread::getState() const {
  return state;
}

unsigned long
Thread::getThreadId() const {
  return thread_id;
}


Thread*
Thread::self() {
  Thread* thread = (Thread*) TlsGetValue(threadStorage);
  if (!thread) {
    // *** memory leak - could use GetExitCodeThread to lazily detect when
    //     to clean up native thread objects
    thread = new Thread(GetCurrentThread(), GetCurrentThreadId());
    TlsSetValue(threadStorage, thread);
  }
  return thread;
}
