#ifndef THREADING_H
#define THREADING_H

#if !defined(PSP) && !defined(QT_CONFIG) && !(__cplusplus > 199711L)
#include <boost/date_time.hpp>

#ifdef WIN32
#pragma warning (push)
#pragma warning (disable : 4244)
#endif
#include <boost/thread.hpp>
#ifdef WIN32
#pragma warning (pop)
#endif

#include <boost/thread/mutex.hpp>
#elif defined(PSP)
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include "pspthreadman.h"

#include "../include/DebugRoutines.h"
#include "../include/JLogger.h"

namespace boost
{
    /**
    ** PSP specific variant of a boost mutex & scoped_lock
    */

    template <class Mutex>
    struct unique_lock
    {
        unique_lock(Mutex& inMutex) : mMutex(&inMutex)
        {
            mMutex->lock();
        }

        ~unique_lock()
        {
            mMutex->unlock();
        }

        Mutex* mMutex;
    };

    class mutex
    {
    public:

        typedef unique_lock<mutex> scoped_lock;

        mutex()
        {
            mID = sceKernelCreateSema("Unnamed", 0, 1, 1, 0);
        }

        ~mutex()
        {
            sceKernelDeleteSema(mID);
        }

        void lock()
        {
            int result = sceKernelWaitSema(mID, 1, 0);
            if (result < 0)
            {
                LOG("Semaphore error on lock acquire, mutex id: ");
                LOG((char*)mID);
            }
        }

        void unlock()
        {
            int result = sceKernelSignalSema(mID, 1);
            if (result < 0)
            {
                LOG("Semaphore error on lock release, mutex id: ");
                LOG((char*)mID);
            }
        }

        int mID;

    private:
        mutex(mutex const&);
        mutex& operator=(mutex const&);
    };



    class recursive_mutex
    {
    public:

        typedef unique_lock<recursive_mutex> scoped_lock;

        recursive_mutex() : mRecursionCount(0)
        {
            mID = sceKernelCreateSema("Unnamed", 0, 1, 1, 0);
        }

        ~recursive_mutex()
        {
            sceKernelDeleteSema(mID);
        }

        void lock()
        {
            int currentThreadID = sceKernelGetThreadId();

            if (!try_recursive_lock(currentThreadID))
            {
                int result = sceKernelWaitSema(mID, 1, 0);
                if (result < 0)
                {
                    LOG("Semaphore error on lock acquire, mutex id: ");
                    LOG((char*)mID);
                }
                else
                {
                    mThreadID = currentThreadID;
                    mRecursionCount = 1;
                }
            }
        }

        void unlock()
        {

            if(!--mRecursionCount)
            {
                mThreadID = 0;

                int result = sceKernelSignalSema(mID, 1);
                if (result < 0)
                {
                    LOG("Semaphore error on lock release, mutex id: ");
                    LOG((char*)mID);
                }
            }

        }

        int mID;
        int mThreadID;
        volatile int mRecursionCount;

    private:
        recursive_mutex(recursive_mutex const&);
        recursive_mutex& operator=(recursive_mutex const&);

        bool try_recursive_lock(int inThreadID)
        {
            if (mThreadID == inThreadID)
            {
                ++mRecursionCount;
                return true;
            }
            return false;
        }
    };


    /**
    ** Emulating boost::thread configuration glue, with some shortcuts
    ** This detail namespace is a distillation of boost's thread.hpp, thread_data.hpp.
    */
    namespace detail
    {
        struct thread_data_base
        {
            thread_data_base()
            {
            }

            virtual ~thread_data_base()
            {
            }

            virtual void run() = 0;
        };

        typedef boost::shared_ptr<detail::thread_data_base> thread_data_ptr;

        template<typename F>
        class thread_data : public detail::thread_data_base
        {
        public:
            thread_data(F f_) : f(f_)
            {
            }

            void run()
            {
                f();
            }

        private:
            F f;

            void operator=(thread_data&);
            thread_data(thread_data&);
        };

    } //namespace detail


    /**
    ** A simplistic implementation of boost::thread, using pspsdk calls for the thread invocation.
    **
    ** The intent of its usage is this form only:
    ** mWorkerThread = boost::thread(ThreadProc, this);
    ** where ThreadProc is a static member function of the 'this' class,eg:
    ** static void FOO::ThreadProc(void* inParam)
    ** {
    **		FOO* instance = reinterpret_cast<FOO*>(inParam);
    **		// now you have class instance data available...
    ** }
    **
    ** Any other variant of a thread proc with more than one param is unimplemented.
    */
    class thread
    {
        /*
        ** Helper class for sceKernelStartThread, which passes args by value, not by reference
        ** We use this struct to wrap any pointers that we want to pass to the worker thread.
        */
        struct CallbackData
        {
            CallbackData(detail::thread_data_ptr inThreadInfo)
                : mThreadInfo(inThreadInfo)
            {
            }

            detail::thread_data_ptr mThreadInfo;
        };

    public:

        thread()
        {
        }

        template <class F,class A1>
        thread(F f, A1 a1) : mThreadInfo(make_thread_info(boost::bind(boost::type<void>(), f, a1)))
        {
            LOG("Calling bind on threadproc func");
            CallbackData callbackData(mThreadInfo);

            LOG("Creating SCE Thread");
            mThreadProcID = sceKernelCreateThread( typeid(a1).name(), thread::ThreadProc, 0x15, 0x40000, PSP_THREAD_ATTR_USER, NULL);
            if (mThreadProcID > 0)
            {
                sceKernelStartThread(mThreadProcID, sizeof(CallbackData), &callbackData);
            }
        }

        ~thread()
        {
        }

        void join()
        {
            sceKernelTerminateDeleteThread(mThreadProcID);
        }

    private:

        static int ThreadProc(SceSize args, void *inParam)
        {
            LOG("Entering thread::ThreadProc");
            CallbackData* callbackData = reinterpret_cast<CallbackData* >(inParam);
            if (callbackData)
            {
                callbackData->mThreadInfo->run();
            }

            return 0;
        }

        template<typename F>
        static inline detail::thread_data_ptr make_thread_info(F f)
        {
            return detail::thread_data_ptr(new detail::thread_data<F>(f));
        }

        detail::thread_data_ptr mThreadInfo;
        SceUID mThreadProcID;
    };

    namespace posix_time
    {
        typedef unsigned int milliseconds;
    }

    /**
    ** boost's platform neutral sleep call.
    */
    namespace this_thread
    {
        inline void sleep(boost::posix_time::milliseconds const& time)
        {
            sceKernelDelayThread(time * 1000);
        }
    }
}

#elif defined(QT_CONFIG) && (__cplusplus <= 199711L)

#include <QMutex>
#include <QThread>
#include <QSharedPointer>

#include "../include/JLogger.h"

namespace boost
{
    template <class Mutex>
    struct unique_lock
    {
        unique_lock(Mutex& inMutex) : mMutex(&inMutex)
        {
            mMutex->lock();
        }

        ~unique_lock()
        {
            mMutex->unlock();
        }

        Mutex* mMutex;
    };

    class mutex
    {
    public:

        typedef unique_lock<mutex> scoped_lock;

        mutex()
          : mQMutex()
        {
        }

        ~mutex()
        {
        }

        void lock()
        {
            mQMutex.lock();
        }

        void unlock()
        {
            mQMutex.unlock();
        }

        QMutex mQMutex;

    private:
        mutex(mutex const&);
        mutex& operator=(mutex const&);
    };



    class recursive_mutex
    {
    public:

        typedef unique_lock<recursive_mutex> scoped_lock;

        recursive_mutex() : mQMutex(QMutex::Recursive)
        {
        }

        ~recursive_mutex()
        {
        }

        void lock()
        {
          mQMutex.lock();
        }

        void unlock()
        {
          mQMutex.unlock();
        }

        QMutex mQMutex;

    private:
        recursive_mutex(recursive_mutex const&);
        recursive_mutex& operator=(recursive_mutex const&);
    };


    /**
    ** Emulating boost::thread configuration glue, with some shortcuts
    ** This detail namespace is a distillation of boost's thread.hpp, thread_data.hpp.
    */
    namespace detail
    {
        struct thread_data_base
        {
            thread_data_base()
            {
            }

            virtual ~thread_data_base()
            {
            }

            virtual void run() = 0;
        };


        typedef QSharedPointer<detail::thread_data_base> thread_data_ptr;

        template<typename F, typename A1>
        class thread_data : public detail::thread_data_base
        {
        public:
            thread_data(F f_, A1 a1_) : f(f_), a1(a1_)
            {
            }

            void run()
            {
                f(a1);
            }

        private:
            F f;
            A1 a1;

            void operator=(thread_data&);
            thread_data(thread_data&);
        };

    } //namespace detail

    class threadImpl : public QThread
    {
      detail::thread_data_ptr mThreadInfo;
    public:
      threadImpl(detail::thread_data_ptr threadInfo) : mThreadInfo(threadInfo)
      {
        setTerminationEnabled();
      };
      static void mymsleep(unsigned long msecs)
      {
          QThread* currentThread = QThread::currentThread();
          if(currentThread)
              currentThread->msleep(msecs);
      }
    protected:
      void run()
      {
        LOG("Entering thread::run");
        mThreadInfo->run();
      }
    };

    /**
    ** A simplistic implementation of boost::thread, using QThread.
    **
    */
    class thread
    {
        /*
        ** Helper class for sceKernelStartThread, which passes args by value, not by reference
        ** We use this struct to wrap any pointers that we want to pass to the worker thread.
        */
        struct CallbackData
        {
            CallbackData(detail::thread_data_ptr inThreadInfo)
                : mThreadInfo(inThreadInfo)
            {
            }

            detail::thread_data_ptr mThreadInfo;
        };

    public:

        thread()
        {
        }

        template <class F,class A1>
        thread(F f, A1 a1) : mThreadInfo(make_thread_info(f, a1))
        {
            mpThread = new threadImpl(mThreadInfo);
            LOG("Calling start func");
            mpThread->start(QThread::LowPriority);
        }

        ~thread()
        {
        }

        void join()
        {
          mpThread->terminate();
        }

    private:
        template<typename F, typename A1>
        static inline detail::thread_data_ptr make_thread_info(F f, A1 a1)
        {
            return detail::thread_data_ptr(new detail::thread_data<F, A1>(f, a1));
        }

        detail::thread_data_ptr mThreadInfo;
        threadImpl* mpThread;
    };

    namespace posix_time
    {
        typedef unsigned int milliseconds;
    }

    /**
    ** boost's platform neutral sleep call.
    */
    namespace this_thread
    {
        inline void sleep(boost::posix_time::milliseconds const& time)
        {
          threadImpl::mymsleep(time);
        }
    }
}

#elif (__cplusplus > 199711L)

#include <thread>
#include <mutex>

namespace boost
{
    typedef std::thread thread;

    template <class Mutex>
    struct unique_lock
    {
        unique_lock(Mutex& inMutex) : mMutex(&inMutex)
        {
            mMutex->lock();
        }

        ~unique_lock()
        {
            mMutex->unlock();
        }

        Mutex* mMutex;
    };

    class mutex
    {
    public:

        typedef unique_lock<mutex> scoped_lock;

        mutex()
          : mQMutex()
        {
        }

        ~mutex()
        {
        }

        void lock()
        {
            mQMutex.lock();
        }

        void unlock()
        {
            mQMutex.unlock();
        }

        std::mutex mQMutex;

    private:
        mutex(mutex const&);
        mutex& operator=(mutex const&);
    };

    namespace posix_time
    {
        typedef unsigned int milliseconds;
    }

    /**
    ** boost's platform neutral sleep call.
    */
    namespace this_thread
    {
        inline void sleep(boost::posix_time::milliseconds const& time)
        {
            std::this_thread::sleep_for(std::chrono::milliseconds(time));
        }
    }
}

#endif

#endif // THREADING_H
