#include "CtrlCore.h"

#define LLOG(x)    // if(!IsMainThread()) DLOG((int)GetCurrentThreadId() << " " << x)

NAMESPACE_UPP

#ifdef _MULTITHREADED

static int         NonMain;
static StaticMutex NonMainLock; 

void EnterGuiMutex()
{
	LLOG("Thread " << IsMainThread() << " trying to lock" << ", nonmain: " << NonMain);
	bool nonmain = !IsMainThread();
	if(nonmain)
		NonMainLock.Enter();
	EnterGMutex();
	if(nonmain)
		NonMain++;
	LLOG("Thread " << IsMainThread() << " LOCK" << ", nonmain: " << NonMain);
}

void LeaveGuiMutex()
{
	LLOG("Thread " << IsMainThread() << " trying to unlock" << ", nonmain: " << NonMain);
	bool nonmain = !IsMainThread();
	if(nonmain)
		NonMain--;
	LeaveGMutex();
	if(nonmain)
		NonMainLock.Leave();
	LLOG("Thread " << IsMainThread() << " UNLOCK" << ", nonmain: " << NonMain);
}

struct Ctrl::CallBox {
	Semaphore sem;
	Callback  cb;
};

void Ctrl::PerformCall(Ctrl::CallBox *cbox)
{
	cbox->cb();
	LLOG("Sem release");
	cbox->sem.Release();
}

Callback    Ctrl::CtrlCall;
static Mutex CtrlCallMutex;

void WakeUpGuiThread();

bool Ctrl::DoCall()
{
	LLOG("DoCall");
	GuiLock __;
	{
		Mutex::Lock __(CtrlCallMutex);
		CtrlCall();
		CtrlCall.Clear();
	}
	LLOG("--- DoCall, nonmain: " << NonMain);
	return NonMain;
}

void Ctrl::ICall(Callback cb)
{
	LLOG("Ctrl::Call " << IsMainThread() << ", nonmain: " << NonMain);
	if(IsMainThread())
		cb();
	else {
		GuiLock __;
		CallBox cbox;
		cbox.cb = cb;
		{
			Mutex::Lock __(CtrlCallMutex);
			CtrlCall = callback1(PerformCall, &cbox);
		}
		int level = LeaveGMutexAll();
		WakeUpGuiThread();
		LLOG("Waiting for semaphore");
		if(!Thread::IsShutdownThreads())
			cbox.sem.Wait();
		EnterGMutex(level);
	}
	LLOG("-- Ctrl::Call " << IsMainThread());
}

void Ctrl::Call(Callback cb)
{
	if(IsMainThread())
		cb();
	else {
		GuiLock __;
		CallBox cbox;
		cbox.cb = cb;
		UPP::PostCallback(callback1(PerformCall, &cbox));
		int n = NonMain;
		NonMain = 0;
		for(int i = 0; i < n; i++)
			NonMainLock.Leave();
		int level = LeaveGMutexAll();
		WakeUpGuiThread();
		if(!Thread::IsShutdownThreads())
			cbox.sem.Wait();
		for(int i = 0; i < n; i++)
			NonMainLock.Enter();
		EnterGMutex(level);
		NonMain = n;
	}
}

#else

bool Ctrl::DoCall()
{
	return false;
}

void Ctrl::ICall(Callback cb)
{
	cb();
}

void Ctrl::Call(Callback cb)
{
	cb();
}
#endif

void Ctrl::GuiSleep(int ms)
{
	Call(callback1(&Ctrl::GuiSleep0, ms));
}

void Ctrl::WndDestroy()
{
	ICall(callback(this, &Ctrl::WndDestroy0));
}

void Ctrl::WndShow(bool b)
{
	ICall(THISBACK1(WndShow0, b));
}

void Ctrl::WndUpdate()
{
	ICall(THISBACK(WndUpdate0));
}

void Ctrl::SetWndForeground()
{
	ICall(THISBACK(SetWndForeground0));
}

bool Ctrl::WndEnable(bool b)
{
	ICall(THISBACK1(WndEnable0, &b));
	return b;
}

bool Ctrl::SetWndFocus()
{
	bool b;
	ICall(THISBACK1(SetWndFocus0, &b));
	return b;
}

void Ctrl::WndInvalidateRect(const Rect& r)
{
	ICall(THISBACK1(WndInvalidateRect0, r));
}

void Ctrl::WndSetPos(const Rect& rect)
{
	ICall(THISBACK1(WndSetPos0, rect));
}

void Ctrl::WndUpdate(const Rect& r)
{
	ICall(THISBACK1(WndUpdate0r, r));
}

void  Ctrl::WndScrollView(const Rect& r, int dx, int dy)
{
	ICall(THISBACK3(WndScrollView0, r, dx, dy));
}

void Ctrl::EventLoop(Ctrl *ctrl)
{
	Call(callback1(&Ctrl::EventLoop0, ctrl));
}

END_UPP_NAMESPACE
