/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "threads/threads.hh"
#include "error/error.hh"
#include "init/init.hh"
#include "main/main.hh"

#include 
#include 
#include 

static w32 i4_thread_count=0;
static i4_critical_section_class i4_thread_lock;
static i4_critical_section_class thread_list_lock;

struct thread_node
{
  HANDLE h;
  w32 thread_id;
  thread_node *next;
  void *base, *top;

  thread_node(HANDLE h, w32 thread_id, void *base, void *top, thread_node *next)
    : h(h), thread_id(thread_id), next(next), base(base), top(top) {}

};

static thread_node *thread_list=0;

int i4_main_thread_id;

void i4_wait_threads()  // waits for all threads to terminate (don't call from a thread!)
{
  while (i4_thread_count!=0)
    i4_thread_yield();
}



static i4_thread_func_type i4_thread_to_start=0;
static int i4_thread_size;

void remove_thread(int id)
{
  thread_list_lock.lock();

  thread_node *p=0;
  if (thread_list->thread_id==id)
  {
    p=thread_list;
    thread_list=thread_list->next;
  }
  else
  {
    for (thread_node *q=thread_list; q->next->thread_id!=id; q=q->next);
    p=q->next;
    q->next=p->next;
  }

  CloseHandle(p->h);
  delete p;
  thread_list_lock.unlock();
}


void i4_thread_starter(void *arg)
{
  i4_thread_func_type start=i4_thread_to_start;
  int size=i4_thread_size;
  size-=200;
  i4_thread_to_start=0;

  thread_list_lock.lock();
  w32 thread_id=GetCurrentThreadId();

  HANDLE h;
  DuplicateHandle(GetCurrentProcess(),
                  GetCurrentThread(),
                  GetCurrentProcess(),
                  &h,
                  DUPLICATE_SAME_ACCESS,
                  FALSE,
                  DUPLICATE_SAME_ACCESS);

                          
  thread_node *p=new thread_node(h, thread_id, 
                                 (void *)&size, 
                                 (void *)(((char *)&size)-size),
                                 thread_list);


  thread_list=p;
  thread_list_lock.unlock();



  start(arg);

  remove_thread(p->thread_id);

  i4_thread_lock.lock();
  i4_thread_count--;
  i4_thread_lock.unlock();

  _endthread();
}

void i4_add_thread(i4_thread_func_type fun, w32 stack_size, void *arg_list)

{
  while (i4_thread_to_start!=0)
    i4_thread_yield();

  i4_thread_to_start=fun;
  i4_thread_size=stack_size;
 
  i4_thread_lock.lock();
  i4_thread_count++;
  i4_thread_lock.unlock();

  _beginthread(i4_thread_starter, stack_size, arg_list);
}

void i4_thread_yield()
{
  Sleep(0);
}


int i4_get_thread_id()
{
  return GetCurrentThreadId();
}




void i4_suspend_other_threads()
{
  thread_list_lock.lock();

  w32 thread_id=GetCurrentThreadId();
  for (thread_node *p=thread_list; p; p=p->next)
    if (p->thread_id!=thread_id)      
      SuspendThread(p->h);

  thread_list_lock.unlock();
}

void i4_resume_other_threads()
{
  thread_list_lock.lock();

  w32 thread_id=GetCurrentThreadId();
  for (thread_node *p=thread_list; p; p=p->next)
    if (p->thread_id!=thread_id)      
      ResumeThread(p->h);

  thread_list_lock.unlock();
}


int i4_get_main_thread_id()
{
  return i4_main_thread_id;
}

i4_bool i4_get_first_thread_id(int &id);
i4_bool i4_get_next_thread_id(int last_id, int &id);

int i4_get_first_thread_id() { return thread_list->thread_id; }

i4_bool i4_get_next_thread_id(int last_id, int &id)
{
  for (thread_node *p=thread_list; p; p=p->next)
    if (p->thread_id==last_id)
      if (p->next)
      {
        id=p->next->thread_id;
        return i4_T;
      }
      else
        return i4_F;
  return i4_F;
}


void i4_get_thread_stack(int thread_id, void *&base, void *&top)
{
  base=0;
  top=0;

  if (thread_id==i4_main_thread_id)
  {
    base=i4_stack_base;
    if (i4_get_thread_id()!=i4_main_thread_id)
      i4_error("Can't get main thread stack from thread");
    
    int t;
    top=(void *)(&t);      
  }
  else
  {
    for (thread_node *p=thread_list; p; p=p->next)
      if (p->thread_id==thread_id)
      {
        base=p->base;
        top=p->top;
      }
  }
}



class thread_initer : public i4_init_class
{
public:
  virtual int init_type() { return I4_INIT_TYPE_THREADS; }
  void init()
  {
    i4_main_thread_id=i4_get_thread_id();     
    HANDLE h;
    DuplicateHandle(GetCurrentProcess(),
                    GetCurrentThread(),
                    GetCurrentProcess(),
                    &h,
                    DUPLICATE_SAME_ACCESS,
                    FALSE,
                    DUPLICATE_SAME_ACCESS);
                          
    thread_list=new thread_node(h, i4_main_thread_id, 
                                i4_stack_base, i4_stack_base, 0);
  }

  void uninit()
  {
    remove_thread(i4_main_thread_id);
  }

};

static thread_initer thread_initer_instance;



////////////// Critical section stuff
i4_critical_section_class::i4_critical_section_class() 
{ 
  data[0]=0;
}

void I4_FAST_CALL i4_critical_section_class::lock()
{ 
  __asm
  {
    start:      
      bts [ecx], 0
      jnc success
      call i4_thread_yield   // give up our time-slice
      jmp start
    success:
  }
}

void I4_FAST_CALL i4_critical_section_class::unlock() 
{ 
  __asm mov [ecx], 0
}

i4_critical_section_class::~i4_critical_section_class()
{

}


///////////// signal stuff
i4_signal_object::i4_signal_object(char *name) 
{ 
  char buf[100];
  sprintf(buf,"%s-%d",name,_getpid());     // make sure name doesn't interfere with other
  *((HANDLE *)data)=CreateSemaphore(0, 0, 1, buf); // processes using this library
}

void i4_signal_object::wait_signal()           
{ 
  WaitForSingleObject(*((HANDLE *)data), INFINITE);  
}


void i4_signal_object::signal()                
{ 
  ReleaseSemaphore(*((HANDLE *)data), 1, 0);  
}

i4_signal_object::~i4_signal_object()          
{ 
  CloseHandle(*((HANDLE *)data)); 
}

i4_bool i4_threads_supported() { return i4_T; }


void i4_set_thread_priority(int thread_id, i4_thread_priority_type priority)
{
  thread_list_lock.lock();

  for (thread_node *p=thread_list; p && p->thread_id!=thread_id; p=p->next);
  if (p)
  {
    switch (priority)
    {
      case I4_THREAD_PRIORITY_HIGH :
        SetThreadPriority(p->h, THREAD_PRIORITY_HIGHEST);
        break;

      case I4_THREAD_PRIORITY_NORMAL :
        SetThreadPriority(p->h, THREAD_PRIORITY_NORMAL);
        break;

      case I4_THREAD_PRIORITY_LOW :
        SetThreadPriority(p->h, THREAD_PRIORITY_BELOW_NORMAL);
    }
  }

  thread_list_lock.unlock();
}