/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* RCThread.cpp - C++ wrapper on NSPR */

#include "rcthread.h"
#include "rcinrval.h"

#include <prmem.h>
#include <prlog.h>
#include <stdio.h>
#include <prinit.h>

static RCPrimordialThread *primordial = NULL;

void nas_Root(void *arg)
{
    RCThread *him = (RCThread*)arg;
    while (RCThread::ex_unstarted == him->execution) {
        (void)PR_Sleep(PR_INTERVAL_NO_TIMEOUT);    /* wait for Start() */
    }
    him->RootFunction();  /* he gets a self reference */
    if (PR_UNJOINABLE_THREAD == PR_GetThreadState(him->identity)) {
        delete him;
    }
}  /* nas_Root */

RCThread::~RCThread() { }

RCThread::RCThread(): RCBase() { }

RCThread::RCThread(const RCThread&): RCBase()
{
    PR_NOT_REACHED("Cannot call thread copy constructor");
}  /* RCThread::RCThread */

RCThread::RCThread(
    RCThread::Scope scope, RCThread::State join, PRUint32 stackSize):
    RCBase()
{
    execution = ex_unstarted;
    identity = PR_CreateThread(
                   PR_USER_THREAD, nas_Root, this,
                   PR_GetThreadPriority(PR_GetCurrentThread()),
                   (PRThreadScope)scope, (PRThreadState)join, stackSize);
}  /* RCThread::RCThread */

void RCThread::operator=(const RCThread&)
{
    PR_NOT_REACHED("Cannot call thread assignment operator");
}  /* RCThread::operator= */


PRStatus RCThread::Start()
{
    PRStatus rv;
    /* This is an unsafe check, but not too critical */
    if (RCThread::ex_unstarted == execution)
    {
        execution = RCThread::ex_started;
        rv = PR_Interrupt(identity);
        PR_ASSERT(PR_SUCCESS == rv);
    }
    else
    {
        rv = PR_FAILURE;
        PR_SetError(PR_INVALID_STATE_ERROR, 0);
    }
    return rv;
}  /* RCThread::Start */

PRStatus RCThread::Join()
{
    PRStatus rv;
    if (RCThread::ex_unstarted == execution)
    {
        rv = PR_FAILURE;
        PR_SetError(PR_INVALID_STATE_ERROR, 0);
    }
    else {
        rv = PR_JoinThread(identity);
    }
    if (PR_SUCCESS == rv) {
        delete this;
    }
    return rv;
}  /* RCThread::Join */

PRStatus RCThread::Interrupt()
{
    PRStatus rv;
    if (RCThread::ex_unstarted == execution)
    {
        rv = PR_FAILURE;
        PR_SetError(PR_INVALID_STATE_ERROR, 0);
    }
    else {
        rv = PR_Interrupt(identity);
    }
    return rv;
}  /* RCThread::Interrupt */

void RCThread::ClearInterrupt() {
    PR_ClearInterrupt();
}

void RCThread::SetPriority(RCThread::Priority new_priority)
{
    PR_SetThreadPriority(identity, (PRThreadPriority)new_priority);
}

PRThread *RCThread::Self()
{
    return PR_GetCurrentThread();
}

RCThread::Scope RCThread::GetScope() const
{
    return (RCThread::Scope)PR_GetThreadScope(identity);
}

RCThread::State RCThread::GetState() const
{
    return (RCThread::State)PR_GetThreadState(identity);
}

RCThread::Priority RCThread::GetPriority() const
{
    return (RCThread::Priority)PR_GetThreadPriority(identity);
}

static void _rc_PDDestructor(RCThreadPrivateData* privateData)
{
    PR_ASSERT(NULL != privateData);
    privateData->Release();
}

static PRThreadPrivateDTOR _tpd_dtor = (PRThreadPrivateDTOR)_rc_PDDestructor;

PRStatus RCThread::NewPrivateIndex(PRUintn* index)
{
    return PR_NewThreadPrivateIndex(index, _tpd_dtor);
}

PRStatus RCThread::SetPrivateData(PRUintn index)
{
    return PR_SetThreadPrivate(index, NULL);
}

PRStatus RCThread::SetPrivateData(PRUintn index, RCThreadPrivateData* data)
{
    return PR_SetThreadPrivate(index, data);
}

RCThreadPrivateData* RCThread::GetPrivateData(PRUintn index)
{
    return (RCThreadPrivateData*)PR_GetThreadPrivate(index);
}

PRStatus RCThread::Sleep(const RCInterval& ticks)
{
    PRIntervalTime tmo = ticks;
    return PR_Sleep(tmo);
}

RCPrimordialThread *RCThread::WrapPrimordialThread()
{
    /*
    ** This needs to take more care in insuring that the thread
    ** being wrapped is really the primordial thread. This code
    ** is assuming that the caller is the primordial thread, and
    ** there's nothing to insure that.
    */
    if (NULL == primordial)
    {
        /* it doesn't have to be perfect */
        RCPrimordialThread *me = new RCPrimordialThread();
        PR_ASSERT(NULL != me);
        if (NULL == primordial)
        {
            primordial = me;
            me->execution = RCThread::ex_started;
            me->identity = PR_GetCurrentThread();
        }
        else {
            delete me;    /* somebody beat us to it */
        }
    }
    return primordial;
}  /* RCThread::WrapPrimordialThread */

RCPrimordialThread::RCPrimordialThread(): RCThread() { }

RCPrimordialThread::~RCPrimordialThread() { }

void RCPrimordialThread::RootFunction()
{
    PR_NOT_REACHED("Primordial thread calling root function");
}  /* RCPrimordialThread::RootFunction */

PRStatus RCPrimordialThread::Cleanup() {
    return PR_Cleanup();
}

PRStatus RCPrimordialThread::SetVirtualProcessors(PRIntn count)
{
    PR_SetConcurrency(count);
    return PR_SUCCESS;
}  /* SetVirutalProcessors */

RCThreadPrivateData::RCThreadPrivateData() { }

RCThreadPrivateData::RCThreadPrivateData(
    const RCThreadPrivateData& him) { }

RCThreadPrivateData::~RCThreadPrivateData() { }

/* RCThread.c */