//
// OpenThread library, Copyright (C) 2002 - 2003  The Open Thread Group
//
// This library 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.1 of the License, or (at your option) any later version.
//
// This library 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 this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//

//
// PThread.c++ - C++ Thread class built on top of posix threads.
// ~~~~~~~~~~~

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <assert.h>

#if defined __linux || defined __sun || defined __APPLE__
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/unistd.h>
#endif

#if defined (__linux__)
    #include <sched.h>
#endif

#include <OpenThreads/Thread>
#include "PThreadPrivateData.h"

#include <iostream>

using namespace OpenThreads;

extern int errno;
const char *OPENTHREAD_VERSION_STRING = "OpenThreads v1.2preAlpha, Posix Threads (Public Implementation)";

#ifdef DEBUG
# define DPRINTF(arg) printf arg
#else
# define DPRINTF(arg)
#endif

//-----------------------------------------------------------------------------
// Initialize the static unique ids.
//
int PThreadPrivateData::nextId = 0;

//-----------------------------------------------------------------------------
// Initialize thread master priority level
//
Thread::ThreadPriority Thread::s_masterThreadPriority =
                                          Thread::THREAD_PRIORITY_DEFAULT;

bool Thread::s_isInitialized = false;
pthread_key_t PThreadPrivateData::s_tls_key;

struct ThreadCleanupStruct {

    OpenThreads::Thread *thread;
    volatile bool *runflag;

};

//-----------------------------------------------------------------------------
// This cleanup handler is necessary to ensure that the thread will cleanup
// and set its isRunning flag properly.
//
void thread_cleanup_handler(void *arg) {

    ThreadCleanupStruct *tcs = static_cast<ThreadCleanupStruct *>(arg);

    tcs->thread->cancelCleanup();
    *(tcs->runflag) = false;

}

//-----------------------------------------------------------------------------
// Class to support some static methods necessary for pthread's to work
// correctly.
//

namespace OpenThreads {

class ThreadPrivateActions {

    //-------------------------------------------------------------------------
    // We're friendly to Thread, so it can issue the methods.
    //
    friend class Thread;

private:

    //-------------------------------------------------------------------------
    // pthreads standard start routine.
    //
    static void *StartThread(void *data) {

	Thread *thread = static_cast<Thread *>(data);
	PThreadPrivateData *pd =
	    static_cast<PThreadPrivateData *>(thread->_prvData);

        if (pd->cpunum>=0)
        {
            #ifdef __sgi
            
                pthread_setrunon_np( pd->cpunum );
                
            #elif defined (__linux__) && defined(CPU_SET)

                cpu_set_t cpumask;
                CPU_ZERO( &cpumask );
                CPU_SET( pd->cpunum, &cpumask );

                #if defined(COMPILE_USING_TWO_PARAM_sched_setaffinity)
                    sched_setaffinity( 0, &cpumask );
                #else
                    sched_setaffinity( 0, sizeof(cpumask), &cpumask );
                #endif
                
            #endif
        }
        

	ThreadCleanupStruct tcs;
	tcs.thread = thread;
	tcs.runflag = &pd->isRunning;

	// Set local storage so that Thread::CurrentThread() can return the right thing
	int status = pthread_setspecific(PThreadPrivateData::s_tls_key, thread);
	assert(status == 0);

	pthread_cleanup_push(thread_cleanup_handler, &tcs);

#ifdef ALLOW_PRIORITY_SCHEDULING

	//---------------------------------------------------------------------
	// Set the proper scheduling priorities
	//
	SetThreadSchedulingParams(thread);

#endif // ] ALLOW_PRIORITY_SCHEDULING

        pd->isRunning = true;
        thread->run();
        pd->isRunning = false;

	pthread_cleanup_pop(0);

        return 0;

    };

    //-------------------------------------------------------------------------
    // Print information related to thread schduling parameters.
    //
    static void PrintThreadSchedulingInfo(Thread *thread) {

#ifdef ALLOW_PRIORITY_SCHEDULING // [

	if(sysconf(_POSIX_THREAD_PRIORITY_SCHEDULING)) {

	    int status, my_policy, min_priority, max_priority;
	    struct sched_param my_param;

	    status = pthread_getschedparam(thread->getProcessId(),
					   &my_policy,
					   &my_param);

	    if(status != 0) {
		printf("THREAD INFO (%d) : Get sched: %s\n",
		       thread->getProcessId(),
		       strerror(status));
	    } else {
		printf(
		    "THREAD INFO (%d) : Thread running at %s / Priority: %d\n",
		    thread->getProcessId(),
		    (my_policy == SCHED_FIFO ? "SCHEDULE_FIFO"
		     : (my_policy == SCHED_RR ? "SCHEDULE_ROUND_ROBIN"
			: (my_policy == SCHED_OTHER ? "SCHEDULE_OTHER"
			   : "UNKNOWN"))),
		    my_param.sched_priority);

		max_priority = sched_get_priority_max(my_policy);
		min_priority = sched_get_priority_min(my_policy);

		printf(
		    "THREAD INFO (%d) : Max priority: %d, Min priority: %d\n",
		    thread->getProcessId(),
		    max_priority, min_priority);

	    }

	} else {
	    printf(
		"THREAD INFO (%d) POSIX Priority scheduling not available\n",
		thread->getProcessId());
	}

	fflush(stdout);

#endif // ] ALLOW_PRIORITY_SCHEDULING

    }

    //--------------------------------------------------------------------------
    // Set thread scheduling parameters.  Unfortunately on Linux, there's no
    // good way to set this, as pthread_setschedparam is mostly a no-op.
    //
    static int SetThreadSchedulingParams(Thread *thread) {

	int status = 0;

#ifdef ALLOW_PRIORITY_SCHEDULING // [

	if(sysconf(_POSIX_THREAD_PRIORITY_SCHEDULING)) {

	    int th_policy;
	    int max_priority, nominal_priority, min_priority;
	    sched_param th_param;
	    pthread_getschedparam(thread->getProcessId(),
				  &th_policy, &th_param);

#ifndef __linux__

	    switch(thread->getSchedulePolicy()) {

	    case Thread::THREAD_SCHEDULE_FIFO:
		th_policy = SCHED_FIFO;
		break;

	    case Thread::THREAD_SCHEDULE_ROUND_ROBIN:
		th_policy = SCHED_RR;
		break;

	    case Thread::THREAD_SCHEDULE_TIME_SHARE:
		th_policy = SCHED_OTHER;
		break;

	    default:
#ifdef __sgi
		th_policy = SCHED_RR;
#else
		th_policy = SCHED_FIFO;
#endif
		break;
	    };

#else
	    th_policy = SCHED_OTHER;  // Must protect linux from realtime.
#endif

#ifdef __linux__

	    max_priority = 0;
	    min_priority = 20;
	    nominal_priority = (max_priority + min_priority)/2;

#else

	    max_priority = sched_get_priority_max(th_policy);
	    min_priority = sched_get_priority_min(th_policy);
	    nominal_priority = (max_priority + min_priority)/2;

#endif

	    switch(thread->getSchedulePriority()) {

	    case Thread::THREAD_PRIORITY_MAX:
		th_param.sched_priority = max_priority;
		break;

	    case Thread::THREAD_PRIORITY_HIGH:
		th_param.sched_priority = (max_priority + nominal_priority)/2;
		break;

	    case Thread::THREAD_PRIORITY_NOMINAL:
		th_param.sched_priority = nominal_priority;
		break;

	    case Thread::THREAD_PRIORITY_LOW:
		th_param.sched_priority = (min_priority + nominal_priority)/2;
		break;

	    case Thread::THREAD_PRIORITY_MIN:
		th_param.sched_priority = min_priority;
		break;

	    default:
		th_param.sched_priority = max_priority;
		break;

	    }

	    status = pthread_setschedparam(thread->getProcessId(),
					   th_policy,
					   &th_param);


	    if(getenv("OUTPUT_THREADLIB_SCHEDULING_INFO") != 0)
		PrintThreadSchedulingInfo(thread);

	}

#endif // ] ALLOW_PRIORITY_SCHEDULING

	return status;
    };
};

}

//----------------------------------------------------------------------------
//
// Description: Set the concurrency level (no-op)
//
// Use static public
//
int Thread::SetConcurrency(int concurrencyLevel) {

#if defined (__sgi) || defined (__sun)
    return pthread_setconcurrency(concurrencyLevel);
#else
    return -1;
#endif

};

//----------------------------------------------------------------------------
//
// Description: Get the concurrency level
//
// Use static public
//
int Thread::GetConcurrency() {

#if defined (__sgi) || defined (__sun)
    return pthread_getconcurrency();
#else
    return -1;
#endif

};

//----------------------------------------------------------------------------
//
// Decription: Constructor
//
// Use: public.
//
Thread::Thread() {

    if(!s_isInitialized) Init();

    PThreadPrivateData *pd = new PThreadPrivateData();
    pd->stackSize = 0;
    pd->stackSizeLocked = false;
    pd->idSet = false;
    pd->isRunning = false;
    pd->isCanceled = false;
    pd->uniqueId = pd->nextId;
    pd->nextId++;
    pd->threadPriority = Thread::THREAD_PRIORITY_DEFAULT;
    pd->threadPolicy = Thread::THREAD_SCHEDULE_DEFAULT;
    pd->cpunum = -1;

    _prvData = static_cast<void *>(pd);

}

//----------------------------------------------------------------------------
//
// Decription: Destructor
//
// Use: public.
//
Thread::~Thread() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *>(_prvData);

    if(pd->isRunning) {

        std::cout<<"Error: Thread "<<this<<" still running in destructor"<<std::endl;

	//---------------------------------------------------------------------
	// Kill the thread when it is destructed
	//
	cancel();
    }

    delete pd;

}

Thread *Thread::CurrentThread() {

    Thread *thread =
	static_cast<Thread *>(pthread_getspecific(PThreadPrivateData::s_tls_key));

    return thread;

}

//-----------------------------------------------------------------------------
//
// Description: Initialize Threading
//
// Use: public.
//
void Thread::Init() {

    if(s_isInitialized) return;

    // Allocate a key to be used to access thread local storage
    int status = pthread_key_create(&PThreadPrivateData::s_tls_key, NULL);
    assert(status == 0);

#ifdef ALLOW_PRIORITY_SCHEDULING

    //--------------------------------------------------------------------------
    // If we've got priority scheduling, set things to nominal.
    //
    if(sysconf(_POSIX_THREAD_PRIORITY_SCHEDULING)) {

	int max_priority, nominal_priority, min_priority;

	int th_policy;
	sched_param th_param;
	pthread_getschedparam(pthread_self(),
			      &th_policy, &th_param);

	max_priority = sched_get_priority_max(th_policy);
	min_priority = sched_get_priority_min(th_policy);
	nominal_priority = (max_priority + min_priority)/2;

	th_param.sched_priority = nominal_priority;

	pthread_setschedparam(pthread_self(),
			      th_policy,
			      &th_param);

	s_masterThreadPriority = Thread::THREAD_PRIORITY_NOMINAL;

    } else {

	s_masterThreadPriority = Thread::THREAD_PRIORITY_DEFAULT;

    }

#endif // ] ALLOW_PRIORITY_SCHEDULING

    s_isInitialized = true;

}

//-----------------------------------------------------------------------------
//
// Description: Get a unique identifier for this thread.
//
// Use: public
//
int Thread::getThreadId() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);
    return pd->uniqueId;
}

//-----------------------------------------------------------------------------
//
// Description: Get the thread's process id
//
// Use: public
//
size_t Thread::getProcessId() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    if(pd->idSet == false) return (size_t)(pthread_self());

    return (size_t)(pd->tid);
}

//-----------------------------------------------------------------------------
//
// Description: Set the thread's processor affinity
//
// Use: public
//
int Thread::setProcessorAffinity(unsigned int cpunum)
{
    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);
    pd->cpunum = cpunum;
    if (pd->cpunum<0) return -1;
    
#ifdef __sgi

    int status;
    pthread_attr_t thread_attr;

    status = pthread_attr_init( &thread_attr );
    if(status != 0) {
        return status;
     }

    status = pthread_attr_setscope( &thread_attr, PTHREAD_SCOPE_BOUND_NP );
    return status;

#elif defined (__linux__) && defined(CPU_SET)

  
    if (pd->isRunning && Thread::CurrentThread()==this)
    {
        cpu_set_t cpumask;
        CPU_ZERO( &cpumask );
        CPU_SET( pd->cpunum, &cpumask );

        #if defined(COMPILE_USING_TWO_PARAM_sched_setaffinity)
            return sched_setaffinity( 0, &cpumask );
        #else
            return sched_setaffinity( 0, sizeof(cpumask), &cpumask );
        #endif
    }

    return -1;
#else
    return -1;
#endif

}

//-----------------------------------------------------------------------------
//
// Description: Determine if the thread is running
//
// Use: public
//
bool Thread::isRunning() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);
    return pd->isRunning;

}

//-----------------------------------------------------------------------------
//
// Description: Start the thread.
//
// Use: public
//
int Thread::start() {

    int status;
    pthread_attr_t thread_attr;

    status = pthread_attr_init( &thread_attr );
    if(status != 0) {
	return status;
    }

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    size_t defaultStackSize;
    pthread_attr_getstacksize( &thread_attr, &defaultStackSize);
    if(status != 0) {
	return status;
    }

    if(defaultStackSize < pd->stackSize) {

	pthread_attr_setstacksize( &thread_attr, pd->stackSize);
	if(status != 0) {
	    return status;
	}
    }

    //-------------------------------------------------------------------------
    // Now get what we actually have...
    //
    pthread_attr_getstacksize( &thread_attr, &defaultStackSize);
    if(status != 0) {
	return status;
    }

    pd->stackSize = defaultStackSize;

    //-------------------------------------------------------------------------
    // Prohibit the stack size from being changed.
    //
    pd->stackSizeLocked = true;

#ifdef ALLOW_PRIORITY_SCHEDULING

    status = pthread_attr_setinheritsched( &thread_attr,
					   PTHREAD_EXPLICIT_SCHED );

    pthread_attr_setscope(&thread_attr, PTHREAD_SCOPE_SYSTEM);

#endif // ] ALLOW_PRIORITY_SCHEDULING

    if(status != 0) {
	return status;
    }

    status = pthread_create(&(pd->tid), &thread_attr,
                           ThreadPrivateActions::StartThread,
                           static_cast<void *>(this));

    if(status != 0) {
	return status;
    }

    pd->idSet = true;

    return 0;

}

//-----------------------------------------------------------------------------
//
// Description: Alternate thread start routine.
//
// Use: public
//
int Thread::startThread() { return start(); }

//-----------------------------------------------------------------------------
//
// Description: Join the thread.
//
// Use: public
//
int Thread::detach() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);
    return pthread_detach(pd->tid);

}

//-----------------------------------------------------------------------------
//
// Description: Join the thread.
//
// Use: public
//
int Thread::join() {

    void *threadResult = 0; // Dummy var.
    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);
    return pthread_join(pd->tid, &threadResult);

}

//-----------------------------------------------------------------------------
//
// Description: test the cancel state of the thread.
//
// Use: public
//
int Thread::testCancel() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    if(pthread_self() != pd->tid)
	return -1;

    pthread_testcancel();

    return 0;

}


//-----------------------------------------------------------------------------
//
// Description: Cancel the thread.
//
// Use: public
//
int Thread::cancel() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);
    if (pd->isRunning)
    {
        pd->isCanceled = true;
        int status = pthread_cancel(pd->tid);
        return status;
    }
    return 0;
}

//-----------------------------------------------------------------------------
//
// Description: Disable cancelibility
//
// Use: public
//
int Thread::setCancelModeDisable() {

    return pthread_setcancelstate( PTHREAD_CANCEL_DISABLE, 0 );

}

//-----------------------------------------------------------------------------
//
// Description: set the thread to cancel immediately
//
// Use: public
//
int Thread::setCancelModeAsynchronous() {

    int status = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);
    if(status != 0) return status;

    return pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, 0);
}

//-----------------------------------------------------------------------------
//
// Description: set the thread to cancel at the next convienent point.
//
// Use: public
//
int Thread::setCancelModeDeferred() {

    int status = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, 0);
    if(status != 0) return status;

    return pthread_setcancelstate( PTHREAD_CANCEL_ENABLE, 0);

}

//-----------------------------------------------------------------------------
//
// Description: Set the thread's schedule priority (if able)
//
// Use: public
//
int Thread::setSchedulePriority(ThreadPriority priority) {

#ifdef ALLOW_PRIORITY_SCHEDULING

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    pd->threadPriority = priority;

    if(pd->isRunning)
	return ThreadPrivateActions::SetThreadSchedulingParams(this);
    else
	return 0;

#else
    return -1;
#endif

}

//-----------------------------------------------------------------------------
//
// Description: Get the thread's schedule priority (if able)
//
// Use: public
//
int Thread::getSchedulePriority() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    return pd->threadPriority;

}

//-----------------------------------------------------------------------------
//
// Description: Set the thread's scheduling policy (if able)
//
// Use: public
//
int Thread::setSchedulePolicy(ThreadPolicy policy) {

#ifdef ALLOW_PRIORITY_SCHEDULING

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    pd->threadPolicy = policy;

    if(pd->isRunning)
	return ThreadPrivateActions::SetThreadSchedulingParams(this);
    else
	return 0;
#else
    return -1;
#endif

}

//-----------------------------------------------------------------------------
//
// Description: Set the thread's scheduling policy (if able)
//
// Use: public
//
int Thread::getSchedulePolicy() {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    return pd->threadPolicy;

}


//-----------------------------------------------------------------------------
//
// Description: Set the thread's desired stack size
//
// Use: public
//
int Thread::setStackSize(size_t stackSize) {

    PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

    if(pd->stackSizeLocked == true) return 13;  // EACESS

    pd->stackSize = stackSize;

    return 0;

}

//-----------------------------------------------------------------------------
//
// Description: Get the thread's stack size.
//
// Use: public
//
size_t Thread::getStackSize() {

   PThreadPrivateData *pd = static_cast<PThreadPrivateData *> (_prvData);

   return pd->stackSize;

}

//-----------------------------------------------------------------------------
//
// Description:  Print the thread's scheduling information to stdout.
//
// Use: public
//
void Thread::printSchedulingInfo() {

    ThreadPrivateActions::PrintThreadSchedulingInfo(this);

}

//-----------------------------------------------------------------------------
//
// Description:  Yield the processor
//
// Use: protected
//
int Thread::YieldCurrentThread()
{

    return sched_yield();

}

// Description:  sleep
//
// Use: public
//
int Thread::microSleep(unsigned int microsec)
{
    return ::usleep(microsec);
}



//-----------------------------------------------------------------------------
//
// Description:  Get the number of processors
//
int OpenThreads::GetNumberOfProcessors()
{
#if defined(__linux__)
    return sysconf(_SC_NPROCESSORS_CONF);
#else
    return 1;
#endif    
}
