/*
 * kthread.cpp. 
 *
 * Copyright (C) 1999 Waldo Bastian
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define KTR_PTHREADS 


// #Define only one thread kind.... 
#define KTR_NO_THREADS
#ifdef KTR_PTHREADS
#undef KTR_NO_THREADS
#endif

#include "kthread.h"

#include <kapp.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>		// Needed on some systems.
#endif

#ifdef KTR_PTHREADS
#include <pthread.h>
#endif

#include <qsocketnotifier.h>
#include <qdatetime.h>

enum { KTR_PROGRESS, KTR_FINISHED, KTR_CANCELED, KTR_SYNC };

class KThread_data_
{
public:
  KThread         *thread;
#ifdef KTR_PTHREADS
  pthread_t        pthread;
  pthread_mutex_t  mutex;
  pthread_cond_t   cond;
#endif
#ifdef KTR_NO_THREADS
  char             command;
#else
  int              fd[2];
  QSocketNotifier *notifier;
#endif
  int              progress;
  bool             sync;
  bool             canceled;
};

#ifdef KTR_PTHREADS
static void *launch_thread(void *arg)
{
  KThreadWorker *worker = (KThreadWorker *)arg;
  worker->launch(); // Never returns
  return 0;
}
#endif

KThread::KThread(KThreadWorker *worker)
  : mWorker(worker), mRunning(false), 
    mOk(false), mFinished(false), mCanceled(false)
{
   d = new KThread_data_();
   if (worker->d) return; // Already in use!
   worker->d = d;
   d->thread = this;
   d->sync = false;
   d->canceled = false;

#ifndef KTR_NO_THREADS
   // Creating pipe for worker -> main-thread signalisation
   d->fd[0] = d->fd[1] = -1;
   int result;
   result = pipe(d->fd);
   if (result < 0)
   {
      fprintf(stderr, "KThread pipe(): %s\n", strerror(errno));
      return;
   }
   long fdOptions;
   fdOptions = fcntl(d->fd[0], F_GETFL);
   if (fdOptions < 0)
   {
      fprintf(stderr, "KThread fcntl(F_GETFL): %s\n", strerror(errno));
      return;
   }
   result = fcntl(d->fd[0], F_SETFL, fdOptions | O_NONBLOCK);
   if (result < 0)
   {
      fprintf(stderr, "KThread fcntl(F_SETFL): %s\n", strerror(errno));
      return;
   }

   d->notifier = new QSocketNotifier(d->fd[0], QSocketNotifier::Read);
   d->notifier->setEnabled(true);
   connect(d->notifier, SIGNAL(activated(int)),
           this, SLOT(slotAttention(int))); 
#endif

#ifdef KTR_PTHREADS
   pthread_mutex_init(&(d->mutex), 0);
   pthread_cond_init(&(d->cond), 0);
#endif
   
}

KThread::~KThread()
{
#ifndef KTR_NO_THREADS
   int result;
   delete d->notifier;
   d->notifier = 0;
   if (d->fd[0] != -1)
   {
      result = close(d->fd[0]);
      if (result < 0)
      {
         fprintf(stderr, "KThread close(d->fd[0]): %s\n", strerror(errno));
         return;
      }
      d->fd[0] = -1;
   }
   if (d->fd[1] != -1)
   {
      result = close(d->fd[1]);
      if (result < 0)
      {
         fprintf(stderr, "KThread close(d->fd[1]): %s\n", strerror(errno));
         return;
      }
      d->fd[1] = -1;
   }
#endif

#ifdef KTR_PTHREADS
   pthread_mutex_destroy( &(d->mutex) );
#endif

   delete d;
}

bool 
KThread::supported()
{
#ifdef KTR_NO_THREADS
   return false;
#else
   return true;
#endif
}

void
KThread::start()
{
   kapp->processEvents();
   kapp->syncX();
   if (mRunning) return;
#ifdef KTR_NO_THREADS
   mWorker->launch();
#endif
#ifdef KTR_PTHREADS
   int result = pthread_create(&(d->pthread), 0, launch_thread, (void *) mWorker);
   if (result < 0)
   {
      fprintf(stderr, "KThread pthread_exec(): %s\n", strerror(errno));
      return;
   }
#endif
}

void
KThread::cancel(bool wait)
{
   d->canceled = true;
   // The following assignment must come last 
   d->sync = true;
   if (wait) finish(0);
}

bool 
KThread::finish(int msec)
{
   if (mFinished || mCanceled)
      return true;

#ifdef KTR_NO_THREADS
   (void) msec;  // Silence compiler
#else
   QTime startTime = QTime::currentTime();
   
   do {
      int result;
     
      fd_set fdSet;
      fd_set nullSet;

      FD_ZERO(&fdSet);
      FD_ZERO(&nullSet);
      FD_SET(d->fd[0], &fdSet);

      // Wait for waitTime
      if (msec)
      {
         int timeWaited = startTime.msecsTo(QTime::currentTime());
         if (timeWaited < 0)
            break;

         int waitTime = msec-timeWaited;
         if (waitTime < 0)
            break;
         struct timeval time_s;
         time_s.tv_sec = waitTime / 1000;
         time_s.tv_usec = 1000*(waitTime-time_s.tv_sec*1000);
         result = select( d->fd[0]+1, &fdSet, &nullSet, &nullSet, &time_s);
      }
      else
      {
         result = select( d->fd[0]+1, &fdSet, &nullSet, &nullSet, 0);
      }

      if (result >0)
      {
        slotAttention(d->fd[0]);
        if( mFinished || mCanceled)
           return true;
      }
   }
   while (true);
#endif

   return false;
}


//
// This function gets called whenever the worker-thread has something
// to tell to the main-thread.
//
void 
KThread::slotAttention(int)
{
   char command;

#ifdef KTR_NO_THREADS
   command = d->command;
#else
   int result;
   // Read command from pipe
   result = ::read(d->fd[0], &command, sizeof(command));
   if (result < 0)
   {
      fprintf(stderr, "KThread read(d->fd[0]): %s\n", strerror(errno));
      return;
   }
#endif

#ifdef KTR_PTHREADS
   result = pthread_mutex_lock(&(d->mutex));
   if (result < 0)
   {
      fprintf(stderr, "KThread pthread_lock_mutex(): %s\n", strerror(errno));
   }
#endif

   // Handle command
   switch (command)
   {
     case KTR_PROGRESS:
fprintf(stderr, "Emitting progress %d\n", d->progress);
           emit progress(d->progress);
           break;
     case KTR_FINISHED:
#ifdef KTR_PTHREADS
           pthread_join(d->pthread, 0);
#endif        
fprintf(stderr, "Thread has finished\n");
           emit finished();
           mFinished = true;
           break;
     case KTR_CANCELED:
#ifdef KTR_PTHREADS
           pthread_join(d->pthread, 0);
#endif        
fprintf(stderr, "Thread is canceled\n");
           emit canceled();
           mCanceled = true;
           break;
     default:
           fprintf(stderr, "KThread: received unknown command from thread.\n");
           break;
   }
   
   // Let worker thread continue
#ifdef KTR_PTHREADS
   result = pthread_cond_signal(&(d->cond));
   if (result < 0)
   {
      fprintf(stderr, "KThread pthread_cond_signal(): %s\n", strerror(errno));
   }
   result = pthread_mutex_unlock(&(d->mutex));
   if (result < 0)
   {
      fprintf(stderr, "KThread pthread_unlock_mutex(): %s\n", strerror(errno));
   }
#endif
}

void
KThread::notifyMainThread(char command, bool wait)
{
#ifdef KTR_NO_THREADS
   d->command = command;
   slotAttention(0);
#else

   int result;
   // Lock mutex
#ifdef KTR_PTHREADS
   result = pthread_mutex_lock(&(d->mutex));
   if (result < 0)
   {
      fprintf(stderr, "KThreadWorker pthread_lock_mutex(): %s\n", strerror(errno));
   }
#endif // KTR_PTHREADS
    
   // Signal main-thread
   ::write( d->fd[1], &command, sizeof(command)); 

   // Wait for response
#ifdef KTR_PTHREADS
   if (wait)
   {
      result = pthread_cond_wait(&(d->cond), &(d->mutex));
      if (result < 0)
      {
         fprintf(stderr, "KThreadWorker pthread_cond_wait(): %s\n", strerror(errno));
      }
   }
   result = pthread_mutex_unlock(&(d->mutex));
   if (result < 0)
   {
      fprintf(stderr, "KThreadWorker pthread_unlock_mutex(): %s\n", strerror(errno));
   }
#endif // KTR_PTHREADS

#endif // KTR_NO_TRHEADS
}


KThreadWorker::KThreadWorker()
{
   d = 0;
   fprintf(stderr, "KThreadWorker started...\n");
}

KThreadWorker::~KThreadWorker()
{
}

void
KThreadWorker::slotProgress(int progress)
{
   d->progress = progress;

   d->thread->notifyMainThread(KTR_PROGRESS);
}


void
KThreadWorker::launch()
{
  exec();
  syncpoint(); // This is the last cancel-point!
  finished();
  d->thread->notifyMainThread(KTR_FINISHED, false);
#ifdef KTR_PTHREADS
  pthread_exit(0);
#endif
}

void
KThreadWorker::syncpoint()
{
#ifdef KTR_NO_THREADS
  return;
#else
  if (!d->sync) return; // No sync requested, return as fast as possible
  d->sync = false;

  if (d->canceled)
  {
     canceled();
     d->thread->notifyMainThread(KTR_CANCELED, false);
#ifdef KTR_PTHREADS
     pthread_exit(0);
#endif
  }

  d->thread->notifyMainThread(KTR_SYNC);
#endif
}


void
KThreadWorker::exec()
{
   printf("Exec entered\n");
 for(int c = 0; c < 100; c++)
 {
   printf("c = %d\n", c);  
   long b = 0;
   for(long a = 0; a < 10000000; a++)
   {
      b = b + a;
   }
#if 0
   syncpoint();
#else
   if (c < 100)
   {
      slotProgress(c);
   }
#endif
 }
}

void KThreadWorker::finished()
{
printf("Worker finished.\n");
}

void KThreadWorker::canceled()
{
printf("Worker canceled.\n");
}
