#include <pthread.h> // include this first
#include <sched.h>
#include <errno.h>
#include <signal.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <assert.h>
#include "gosy_thread.hpp"
#include "gosy_threadimpl.hpp"
#include "glog_logging.hpp"
#include "gosy_common.hpp"


/***************** cGosyThreadImpl *****************/


cGosyThreadImpl::cGosyThreadImpl(void)
{
    mRunning = false;

    if ( geteuid() != 0) // root
    {
        assert(false); // Must run as root, else we cannot change priority of task
    }
}

cGosyThreadImpl::~cGosyThreadImpl()
{
}

void *cGosyThreadImpl::StartThread(cGosyThread *pThread)
{
    pThread->StartThread();

    return NULL;
}
        

void cGosyThreadImpl::Start(int Priority, cGosyThread* pThread)
{
    int                 RetVal = 0;
                                // SCHED_OTHER (0 @ sched.h) -> Regular, non-realtime
                                // SCHED_RR    (1 @ sched.h) -> Round Robin, Realtime
                                // SCHED_FIFO  (2 @ sched.h) -> First-in First-out, Realtime
#   define              POLICY   (SCHED_FIFO)
    int                 Policy;
    pthread_attr_t      Attr;
    struct sched_param  SchedParam;

    assert( cGosyThread::GetMinPrio() <= Priority 
            && 
            cGosyThread::GetMaxPrio() >= Priority );
#if (POLICY != SCHED_OTHER)
    // SCHED_RR & SCHED_FIFO have prio between 1..99, so add 1 to priority of GOSY
    // as GOSY has prio between 0..15
    Priority = 1;
    assert( sched_get_priority_min(POLICY) <= Priority 
            && 
            sched_get_priority_max(POLICY) >= Priority );
#endif

    if (!mRunning)
    {
        mRunning    = true;

        RetVal = pthread_attr_init(&Attr);        
        assert( RetVal == 0 );

        // Detach state of thread will prevent "zombie processes" as no need
        // to join threads after cancellation.
        RetVal = pthread_attr_setdetachstate(&Attr, PTHREAD_CREATE_DETACHED);
        assert( RetVal == 0 );

#if (POLICY != SCHED_OTHER)
        RetVal = pthread_attr_setschedpolicy( &Attr, POLICY);
        assert( RetVal == 0 );
        SchedParam.sched_priority = Priority;
        RetVal = pthread_attr_setschedparam( &Attr, &SchedParam);  
        assert( RetVal == 0 );
#endif
        RetVal = pthread_create(&mThread, &Attr, (void *(*) (void *))&StartThread, (void *)pThread);
        assert( RetVal == 0 );

        RetVal = pthread_getschedparam(mThread, &Policy, &SchedParam);
        assert( RetVal == 0 );
        assert( Policy == POLICY );
#if (POLICY != SCHED_OTHER)
        assert( SchedParam.sched_priority == Priority );
#endif

        RetVal = pthread_attr_destroy(&Attr);
        assert( RetVal == 0 );
    }
}

int cGosyThreadImpl::SetPriority(int Priority)
{
    struct sched_param  SchedParam;
    int                 RetVal = 0;
    pthread_attr_t      Attr;

    if (Priority < cGosyThread::GetMinPrio()
        ||
        Priority > cGosyThread::GetMaxPrio())
    {
        return GOSY_WRONG_PRIO;
    }

#if (POLICY != SCHED_OTHER)
    assert(mRunning);

    RetVal = pthread_attr_init(&Attr);        
    assert( RetVal == 0 );

    SchedParam.sched_priority = Priority;
    RetVal = pthread_setschedparam(mThread, POLICY, &SchedParam);
    assert( RetVal == 0 );
#endif
}


