/*  PCSX2 - PS2 Emulator for PCs
 *  Copyright (C) 2002-2010  PCSX2 Dev Team
 *
 *  PCSX2 is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU Lesser General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  PCSX2 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 PCSX2.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "Threading.h"
#include "ScopedPtrMT.h"
#include "EventSource.h"

namespace Threading
{

// --------------------------------------------------------------------------------------
//  ThreadDeleteEvent
// --------------------------------------------------------------------------------------
class EventListener_Thread : public IEventDispatcher<int>
{
public:
    typedef int EvtParams;

protected:
    pxThread *m_thread;

public:
    EventListener_Thread()
    {
        m_thread = NULL;
    }

    virtual ~EventListener_Thread() = default;

    void SetThread(pxThread &thr) { m_thread = &thr; }
    void SetThread(pxThread *thr) { m_thread = thr; }

    void DispatchEvent(const int &params)
    {
        OnThreadCleanup();
    }

protected:
    // Invoked by the pxThread when the thread execution is ending.  This is
    // typically more useful than a delete listener since the extended thread information
    // provided by virtualized functions/methods will be available.
    // Important!  This event is executed *by the thread*, so care must be taken to ensure
    // thread sync when necessary (posting messages to the main thread, etc).
    virtual void OnThreadCleanup() = 0;
};

// --------------------------------------------------------------------------------------
// pxThread - Helper class for the basics of starting/managing persistent threads.
// --------------------------------------------------------------------------------------
// This class is meant to be a helper for the typical threading model of "start once and
// reuse many times."  This class incorporates a lot of extra overhead in stopping and
// starting threads, but in turn provides most of the basic thread-safety and event-handling
// functionality needed for a threaded operation.  In practice this model is usually an
// ideal one for efficiency since Operating Systems themselves typically subscribe to a
// design where sleeping, suspending, and resuming threads is very efficient, but starting
// new threads has quite a bit of overhead.
//
// To use this as a base class for your threaded procedure, overload the following virtual
// methods:
//  void OnStart();
//  void ExecuteTaskInThread();
//  void OnCleanupInThread();
//
// Use the public methods Start() and Cancel() to start and shutdown the thread, and use
// m_sem_event internally to post/receive events for the thread (make a public accessor for
// it in your derived class if your thread utilizes the post).
//
// Notes:
//  * Constructing threads as static global vars isn't recommended since it can potentially
//    confuse w32pthreads, if the static initializers are executed out-of-order (C++ offers
//    no dependency options for ensuring correct static var initializations).  Use heap
//    allocation to create thread objects instead.
//
class pxThread
{
    DeclareNoncopyableObject(pxThread);

    friend void pxYield(int ms);

protected:
    wxString m_name; // diagnostic name for our thread.
    pthread_t m_thread;
    uptr m_native_id;     // typically an id, but implementing platforms can do whatever.
    uptr m_native_handle; // typically a pointer/handle, but implementing platforms can do whatever.

    Semaphore m_sem_event;      // general wait event that's needed by most threads
    Semaphore m_sem_startup;    // startup sync tool
    Mutex m_mtx_InThread;       // used for canceling and closing threads in a deadlock-safe manner
    MutexRecursive m_mtx_start; // used to lock the Start() code from starting simultaneous threads accidentally.
    Mutex m_mtx_ThreadName;

    std::atomic<bool> m_detached; // a boolean value which indicates if the m_thread handle is valid
    std::atomic<bool> m_running;  // set true by Start(), and set false by Cancel(), Block(), etc.

    // exception handle, set non-NULL if the thread terminated with an exception
    // Use RethrowException() to re-throw the exception using its original exception type.
    ScopedPtrMT<BaseException> m_except;

    EventSource<EventListener_Thread> m_evtsrc_OnDelete;


public:
    virtual ~pxThread();
    pxThread(const wxString &name = L"pxThread");

    pthread_t GetId() const { return m_thread; }
    u64 GetCpuTime() const;

    virtual void Start();
    virtual void Cancel(bool isBlocking = true);
    virtual bool Cancel(const wxTimeSpan &timeout);
    virtual bool Detach();
    virtual void Block();
    virtual bool Block(const wxTimeSpan &timeout);
    virtual void RethrowException() const;

    void AddListener(EventListener_Thread &evt);
    void AddListener(EventListener_Thread *evt)
    {
        if (evt == NULL)
            return;
        AddListener(*evt);
    }

    void WaitOnSelf(Semaphore &mutex) const;
    void WaitOnSelf(Mutex &mutex) const;
    bool WaitOnSelf(Semaphore &mutex, const wxTimeSpan &timeout) const;
    bool WaitOnSelf(Mutex &mutex, const wxTimeSpan &timeout) const;

    bool IsRunning() const;
    bool IsSelf() const;
    bool HasPendingException() const { return !!m_except; }

    wxString GetName() const;
    void SetName(const wxString &newname);

protected:
    // Extending classes should always implement your own OnStart(), which is called by
    // Start() once necessary locks have been obtained.  Do not override Start() directly
    // unless you're really sure that's what you need to do. ;)
    virtual void OnStart();

    virtual void OnStartInThread();

    // This is called when the thread has been canceled or exits normally.  The pxThread
    // automatically binds it to the pthread cleanup routines as soon as the thread starts.
    virtual void OnCleanupInThread();

    // Implemented by derived class to perform actual threaded task!
    virtual void ExecuteTaskInThread() = 0;

    void TestCancel() const;

    // Yields this thread to other threads and checks for cancellation.  A sleeping thread should
    // always test for cancellation, however if you really don't want to, you can use Threading::Sleep()
    // or better yet, disable cancellation of the thread completely with DisableCancellation().
    //
    // Parameters:
    //   ms - 'minimum' yield time in milliseconds (rough -- typically yields are longer by 1-5ms
    //         depending on operating system/platform).  If ms is 0 or unspecified, then a single
    //         timeslice is yielded to other contending threads.  If no threads are contending for
    //         time when ms==0, then no yield is done, but cancellation is still tested.
    void Yield(int ms = 0)
    {
        pxAssert(IsSelf());
        Threading::Sleep(ms);
        TestCancel();
    }

    void FrankenMutex(Mutex &mutex);

    bool AffinityAssert_AllowFromSelf(const DiagnosticOrigin &origin) const;
    bool AffinityAssert_DisallowFromSelf(const DiagnosticOrigin &origin) const;

    // ----------------------------------------------------------------------------
    // Section of methods for internal use only.

    void _platform_specific_OnStartInThread();
    void _platform_specific_OnCleanupInThread();
    bool _basecancel();
    void _selfRunningTest(const wxChar *name) const;
    void _DoSetThreadName(const wxString &name);
    void _DoSetThreadName(const char *name);
    void _internal_execute();
    void _try_virtual_invoke(void (pxThread::*method)());
    void _ThreadCleanup();

    static void *_internal_callback(void *func);
    static void internal_callback_helper(void *func);
    static void _pt_callback_cleanup(void *handle);
};
}
