// ---------------------------------------------------------------------------
// 
//   Lily Engine alpha
// 
//   Copyright (c) 2010 Clifford Jolly
//     http://cliffjolly.com
//     cliff@cliffjolly.com
// 
// ---------------------------------------------------------------------------
// 
//   This program is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation; either version 2 of the License, or
//   (at your option) any later version.
//  
//   This program 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
//   General Public License for more details.
// 
//   You should have received a copy of the GNU General Public License
//   along with this program; if not, write to the Free Software
//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
//   USA
// -------------------------- END HEADER -------------------------------------

#include "thread.h"

#ifdef WIN32
#include <windows.h>
#include <process.h>
#else
#include <pthread.h>
#endif

#include <vector>
#include <iostream>
using namespace std;

// TODO: Add some idea of thread ownership. Like have the owner thread
// be the only one that can kill or join another thread or something.

// TODO: Add an option to start a thread in a suspended state.

namespace Threads {

// -----------------------------------------------------------------------------
//  Mutex class implementation.
// -----------------------------------------------------------------------------

    struct Mutex::MutexPrivate {

#ifdef WIN32

            // Windows mutexes.
            HANDLE winMutex;

#else

            // Linux/Mac mutexes.
            pthread_mutex_t mutex;

#endif

    };

    Mutex::Mutex(const Mutex &m) {
        mutexPrivate = new MutexPrivate();
        memcpy(mutexPrivate, m.mutexPrivate, sizeof(mutexPrivate));
    }

    Mutex::Mutex(void) {

        mutexPrivate = new MutexPrivate();

#ifdef WIN32

        // Windows
        mutexPrivate->winMutex = CreateMutex(0, FALSE, 0);

#else

        // Linux/Mac
        pthread_mutex_init(&mutexPrivate->mutex, NULL);

#endif

    }

    Mutex::~Mutex(void) {

#ifdef WIN32

        // Windows
        CloseHandle(mutexPrivate->winMutex);

#else

        // Linux/Mac
        pthread_mutex_destroy(&mutexPrivate->mutex);

#endif

        delete mutexPrivate;
    }

    void Mutex::lock(void) {

#ifdef WIN32

        // Windows
        WaitForSingleObject(mutexPrivate->winMutex, INFINITE);

#else

        // Linux/Mac
        pthread_mutex_lock(&mutexPrivate->mutex);

#endif

    }

    void Mutex::unlock(void) {

#ifdef WIN32

        // Windows
        ReleaseMutex(mutexPrivate->winMutex);

#else

        // Linux/Mac
        pthread_mutex_unlock(&mutexPrivate->mutex);

#endif

    }

// -----------------------------------------------------------------------------
//  Thread class implementation.
// -----------------------------------------------------------------------------

    struct Thread::ThreadPrivate {

        public:

            // boost::bind version.
            boost::function<void()> func;
            
            // Simpler callback.
            void (*bareFunc)(void*);
            void *bareData;

#ifdef WIN32

#else

#endif

    };

// Entry point for all threads.
#ifdef WIN32

    unsigned int __stdcall threadStarter(void *data) {
        Thread::ThreadPrivate *threadPrivate = (Thread::ThreadPrivate*)data;

        if(threadPrivate->bareFunc) {
            threadPrivate->bareFunc(threadPrivate->bareData);
        } else {
            threadPrivate->func();
        }

        delete threadPrivate;

        return 0;
    }

#else

    void *threadStarter(void *data) {
        Thread::ThreadPrivate *threadPrivate = (Thread::ThreadPrivate*)data;

        if(threadPrivate->bareFunc) {
            threadPrivate->bareFunc(threadPrivate->bareData);
        } else {
            threadPrivate->func();
        }

        delete threadPrivate;

        return NULL;
    }

#endif

    Thread::Thread(void) {
        threadPrivate = NULL;
    }

    Thread::Thread(const Thread &t) {

        // threadPrivate may be invalid by this point.
        threadPrivate = t.threadPrivate;
        systemThread = t.systemThread;
        done = t.done;
    }

    Thread::Thread(void (*bareFunc)(void*), void *bareData) {
        
        assert(bareFunc);

        threadPrivate = new ThreadPrivate();
        threadPrivate->bareFunc = bareFunc;
        threadPrivate->bareData = bareData;

        finishInit();
    }

    Thread::Thread(boost::function<void()> func) {
    
        threadPrivate = new ThreadPrivate();
        threadPrivate->func = func;
        threadPrivate->bareFunc = NULL;
        threadPrivate->bareData = NULL;

        finishInit();
    }

    void Thread::finishInit(void) {

        done = false;

#ifdef WIN32

        // Windows
        systemThread = (HANDLE)_beginthreadex(0, 0, threadStarter, threadPrivate, CREATE_SUSPENDED, NULL);
        ResumeThread(systemThread);

#else

        // Linux/Mac
        pthread_create(&(systemThread), NULL,
                       threadStarter, threadPrivate);

#endif

    }

    Thread::~Thread(void) {
    }

    void Thread::join(void) {

        if(!done) {

#ifdef WIN32

            // Windows

            DWORD ret = WaitForSingleObject(systemThread, INFINITE);

            // FIXME: This is really just a placeholder with the
            // possible return values in case I need to go back and
            // add some real error handling later.
            switch(ret) {
                case WAIT_ABANDONED:
                case WAIT_TIMEOUT:
                case WAIT_FAILED:
                    // These are all failures, but I'm not sure what
                    // we should do about it.
                    break;

                case WAIT_OBJECT_0:
                    // Success, I think.
                    break;

                default:
                    // Uhhhh...
                    break;
            }

            CloseHandle(systemThread);

#else

            // Linux/Mac
            pthread_join(systemThread, NULL);

#endif
            done = true;

        } else {

            // This thread has already finished, apparently.

        }
    }

    ThreadId Thread::getId(void) {

        if(threadPrivate) {
        
#ifdef WIN32

            // Windows
            return GetThreadId(systemThread);

#else

            // Linux/Mac
            return systemThread;

#endif
        }

        return 0;

    }

    ThreadId getMyId(void) {

#ifdef WIN32

        // Windows
        return GetCurrentThreadId();

#else

        // Linux/Mac
        return pthread_self();

#endif
    }

};
