/*********************************************************
** Copyright (c) 2005
** University of Washington
** Licensed under the terms set forth by University of
** Washington. If you did not sign such a license, you
** are using this software/code illegally and you do not
** have permission to use, modify, or redistribute
** this or any files in this software package.
**
** File: ProgressThread.cpp
**
**********************************************************/
#include "ProgressThread.h"

#ifdef WIN32
#include <Windows.h>
#else
#include <pthread.h>
#endif

CProgressThreadManager CProgressThreadManager::Instance;

CMutex::CMutex()
{
#ifdef WIN32
  m_mutex = CreateMutex(NULL, FALSE, NULL);
  if (NULL == m_mutex)
    {
      m_err = GetLastError();
    }
  else
    {
      m_err = 0;
    }

#else
  pthread_mutexattr_t attr;
  pthread_mutexattr_init(&attr);
  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
  pthread_mutex_init(&m_mutex, &attr);
#endif
}

CMutex::~CMutex()
{
#ifdef WIN32
  if (NULL != m_mutex)
    {
      CloseHandle(m_mutex);
    }

#else
  pthread_mutex_destroy(&m_mutex);
#endif
}

int CMutex::Lock()
{
#ifdef WIN32
  if (NULL == m_mutex)
    {
      return m_err;
    }
  else
    {
      DWORD wait = WaitForSingleObject(m_mutex, 5000L);
      if (WAIT_FAILED == wait)
	{
	  return GetLastError();
	}
    }
  return 0;
#else
  return pthread_mutex_lock(&m_mutex);
#endif
}

int CMutex::Unlock()
{
#ifdef WIN32
  if (NULL == m_mutex)
    return 0;
  if (!ReleaseMutex(m_mutex))
    return GetLastError();
  return 0;
#else
  return pthread_mutex_unlock(&m_mutex);
#endif
}

int CProgressThread::GetProperty(void* pProperty, void** value)
{
    int result = m_mutex.Lock();
    if(0 == result)
      {
	*value = pProperty;
	result = m_mutex.Unlock();
      }
    return result;
}

int CProgressThread::SetProperty(void** pProperty, void* value)
  { 
    int result = m_mutex.Lock();
    if(0 == result)
      {
	*pProperty = value;
	result = m_mutex.Unlock();
      }
    return result;
  }


int CProgressThread::SetReturnData(void* pData, int returnDataSize) { 
  int result = m_mutex.Lock();
  if(0 == result)
    {
      m_pData = pData;
      m_returnDataSize = returnDataSize;
      result = m_mutex.Unlock();
    }
  return result;
}


THREADPROC_RETURN_TYPE ProgressThread(THREADPROC_ARG_TYPE arg)
{
#ifndef WIN32
  pthread_cleanup_push(CleanupProgressThreadForTermination, arg);
  int oldcancelstate;
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldcancelstate);
  int oldcanceltype;
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldcanceltype);
#endif

  if (NULL == arg)
    {
      return (THREADPROC_RETURN_TYPE)1;
    }

  CProgressThread* pPT = (CProgressThread*)arg;
  pPT->SetStartTime();

  THREAD_ID_T tid = THREAD_SELF();

  int status = pPT->Go(tid);

  pPT->SetReturnStatus(status);
  // Update pPT (tell it it's done)
  pPT->SetIsFinished(true);

#ifdef WIN32
  CleanupProgressThreadForTermination(arg);
#else
  pthread_cleanup_pop(1);
#endif

  return 0;
}

int CProgressThread::LockAll()
{
  return m_mutex.Lock();
}

int CProgressThread::UnlockAll()
{
  return m_mutex.Unlock();
}

int CProgressThread::Start()
{
  int status;
  THREAD_ID_T thread;

#ifdef WIN32   
  HANDLE h = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ProgressThread, (LPVOID)this, 0, &thread);
  status = (NULL == h) ? GetLastError() : 0;
  if (status == 0)
    {
      SetIsStarted(true);
      status = SetHandle(h);
    }

#else
  status = pthread_create(&thread, NULL, ProgressThread, (void*)this);
  if(status == 0)
    SetIsStarted(true);
#endif

  if (status == 0)
    {
      status = SetThreadId(thread);
    }
  if(status == 0)
    {
      if(!CProgressThreadManager::SetProgressThread(thread, this))
	{
	  return -1;
	}
    }

  return status;
}

int CProgressThread::Stop()
{
  int status = 0;
#ifdef WIN32
  m_mutex.Lock(); // lock the thread handle as it might be modified
  HANDLE threadHandle = GetHandle();
  if (NULL == threadHandle)
    {
      status = 23;
    }
  if (0 == status)
    {
      // TO DO: Tell the thread to clean itself up.
      if(0 == TerminateThread(threadHandle, 0))
	{
	  status = GetLastError();
	}
    }
  CleanupProgressThreadForTermination(this);
  if (0 == status)
    {
      CloseHandle(threadHandle);
      m_handle = NULL;
    }
  status = m_mutex.Unlock();
#else
  THREAD_ID_T threadid = GetThreadId();
  status = pthread_cancel(threadid);
#endif

  if (0 == status)
    {
      m_mutex.Lock();
#ifdef MAC_OS_X
      m_bFinished = false;
#else
      // on all systems but Mac OS X, the thread is terminated immediately.
      m_bFinished = true;
#endif
      m_bCanceled = true;
      m_mutex.Unlock();
    }

  return status;
}

void CProgressThread::UpdateProgressInfo(double max, double current, int msgid)
{
#ifdef MAC_OS_X
  // Check whether this thread has been cancelled 
  // (because asynchronous pthread_cancel is broken on Mac OS X)
  pthread_testcancel();
#endif

  int status = m_mutex.Lock();
  if (0 == status)
    {
      m_progressInfo.Max = max;
      m_progressInfo.Current = current;
      m_progressInfo.MsgId = msgid;
      status = m_mutex.Unlock();
    }
}

long CProgressThread::GetElapsedTimeMilliseconds()
{
  int status = m_mutex.Lock();
  long result = 0;
  // TO DO: something with this status if it's an error
  if(0 == status)
    {
      if(m_bFinished)
	result = get_time_diff_milliseconds(m_endTime, m_startTime);
      else
	result = get_time_diff_milliseconds(get_time(), m_startTime);

      status = m_mutex.Unlock();
    }
  return result;
}


CProgressThreadManager::~CProgressThreadManager()
{
  for(ProgressThreadMap::iterator i = m_threads.begin(); 
      i != m_threads.end(); 
      i++)
    {
      CProgressThread* pt = (*i).second;
      if (NULL != pt)
	delete pt;
    }
}

CProgressThread* CProgressThreadManager::int_GetProgressThread(THREAD_ID_T tid)
{
  ProgressThreadMap::iterator i = m_threads.find(tid);
  if (i != m_threads.end())
    {
      return (*i).second;
    }
  return NULL;
}

void CProgressThreadManager::int_Erase(THREAD_ID_T tid)
{
  m_threads.erase(tid);
}

bool CProgressThreadManager::int_SetProgressThread(THREAD_ID_T tid, CProgressThread* progThread)
{
  ProgressThreadMap::iterator i = m_threads.find(tid);
  if (i != m_threads.end())
    {
      return false;
    }
  m_threads[tid] = progThread;
  return true;
}

void UpdateProgressThreadProgress(void* arg, double max, double current, int msgid)
{
#ifdef MAC_OS_X
  pthread_testcancel();
#endif

  if(NULL == arg)
    return;
  CProgressThread* pPT = (CProgressThread*)arg;
  pPT->UpdateProgressInfo(max, current, msgid);
}

void CleanupProgressThreadForTermination(void *arg)
{
  if(NULL == arg)
    return;
  CProgressThread* pPT = (CProgressThread*)arg;
  pPT->SetEndTime();
  pPT->CleanupForTermination();
}

void CProgressThread::CleanupForTermination() 
{

}
