/*
    Cross-platform thread handling
    Copyright (C) 2005 Andrew Zabolotny

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "Mthread.h"

#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE_CC__)
#  define MANGOS_PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE
#else
#  define MANGOS_PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP
#endif

#if PLATFORM != PLATFORM_WINDOWS

MThread::MThread ()
{
    tid = 0;
}

MThread::~MThread ()
{
    /* Kill thread if this is not the current thread */
    if (tid && (pthread_self () != tid))
    {
        pthread_cancel (tid);
        pthread_join (tid, NULL);
    }
}

static void *thread_start_routine (void *arg)
{
    MThread *newthr = (MThread *)arg;
    pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    newthr->routine (newthr->arg);
    return NULL;
}

MThread *MThread::Start (void (*routine) (void *arg), void *arg)
{
    MThread *newthr = new MThread ();
    newthr->routine = routine;
    newthr->arg = arg;
    int rc = pthread_create (&newthr->tid, NULL, thread_start_routine, newthr);
    if (rc)
    {
        newthr->DecRef ();
        return NULL;
    }

    return newthr;
}

pthread_mutexattr_t MMutex::attr;
int MMutex::attr_refcount = 0;

MMutex::MMutex ()
{
    if (!attr_refcount++)
    {
        pthread_mutexattr_init (&attr);
        pthread_mutexattr_settype (&attr, MANGOS_PTHREAD_MUTEX_RECURSIVE);
    }

    pthread_mutex_init (&mutex, &attr);
}

MMutex::~MMutex ()
{
    pthread_mutex_destroy (&mutex);
    if (!--attr_refcount)
        pthread_mutexattr_destroy (&attr);
}

bool MMutex::Lock ()
{
    return (pthread_mutex_lock (&mutex) == 0);
}

bool MMutex::TryLock ()
{
    return (pthread_mutex_trylock (&mutex) == 0);
}

void MMutex::Unlock ()
{
    pthread_mutex_unlock (&mutex);
}

MMutex *MMutex::Create ()
{
    return new MMutex ();
}

#else                                                       //windows

MThread::MThread()
{
    th = NULL;
}

MThread::~MThread ()
{
    /* Kill thread if this is not current thread */
    if (th && (GetCurrentThreadId () != id))
    {
        TerminateThread (th, 0);
        WaitForSingleObject (th, INFINITE);
        CloseHandle (th);
    }
}

bool MThread::SetPriority (ThreadPriority prio)
{
    int p;
    switch (prio)
    {
        case IDLE:      p = THREAD_PRIORITY_IDLE; break;
        case LOWER:     p = THREAD_PRIORITY_LOWEST; break;
        case LOW:       p = THREAD_PRIORITY_BELOW_NORMAL; break;
        case NORMAL:    p = THREAD_PRIORITY_NORMAL; break;
        case HIGH:      p = THREAD_PRIORITY_ABOVE_NORMAL; break;
        case HIGHER:    p = THREAD_PRIORITY_HIGHEST; break;
        case REALTIME:  p = THREAD_PRIORITY_TIME_CRITICAL; break;
        default:        p = THREAD_PRIORITY_NORMAL; break;
    }
    return SetThreadPriority (th, p);
}

static DWORD WINAPI thread_start_routine (void *arg)
//static void thread_start_routine (void *arg)
{
    MThread *newthr = (MThread *)arg;
    newthr->id = GetCurrentThreadId ();
    newthr->routine (newthr->arg);
    return 0;
}

MThread *MThread::Start (void (*routine) (void *arg), void *arg)
{
    DWORD dwtid;
    MThread *newthr = new MThread ();
    newthr->routine = routine;
    newthr->arg = arg;
    newthr->th = CreateThread (NULL, WIN32_THREAD_STACK_SIZE, thread_start_routine, newthr, 0, &dwtid);
    //newthr->th = (HANDLE)_beginthread(thread_start_routine, 0, newthr);
    if (!newthr->th)
    {
        newthr->DecRef ();
        return NULL;
    }
    return newthr;
}

MMutex::MMutex ()
{
    sem = CreateMutex (NULL, FALSE, NULL);
}

MMutex::~MMutex ()
{
    CloseHandle (sem);
}

bool MMutex::Lock ()
{
    return (WaitForSingleObject (sem, INFINITE) != WAIT_FAILED);
}

bool MMutex::TryLock ()
{
    DWORD state = WaitForSingleObject (sem, 0);
    return (state == WAIT_OBJECT_0) && (state != WAIT_ABANDONED);
}

void MMutex::Unlock ()
{
    ReleaseMutex (sem);
}

MMutex *MMutex::Create ()
{
    MMutex *mutex = new MMutex ();
    if (!mutex->sem)
    {
        mutex->DecRef ();
        return NULL;
    }
    return mutex;
}
#endif
