#include "stdafx.h"
#include "Emu/SysCalls/SysCalls.h"

extern Module sysPrxForUser;

static const u32 PPU_THREAD_ID_INVALID = 0xFFFFFFFFU;
enum
{
	SYS_PPU_THREAD_ONCE_INIT,
	SYS_PPU_THREAD_DONE_INIT,
};

int sys_ppu_thread_exit(int errorcode)
{
	if(errorcode == 0)
	{
		sysPrxForUser.Log("sys_ppu_thread_exit(errorcode=%d)", errorcode);
	}
	else
	{
		sysPrxForUser.Warning("sys_ppu_thread_exit(errorcode=%d)", errorcode);
	}
	
	PPUThread& thr = GetCurrentPPUThread();
	thr.SetExitStatus(errorcode);
	Emu.GetCPU().RemoveThread(thr.GetId());

	return CELL_OK;
}

int sys_ppu_thread_yield()
{
	sysPrxForUser.Log("sys_ppu_thread_yield()");	
	return CELL_OK;
}

int sys_ppu_thread_join(u32 thread_id, u32 vptr_addr)
{
	sysPrxForUser.Warning("sys_ppu_thread_join(thread_id=%d, vptr_addr=0x%x)", thread_id, vptr_addr);

	CPUThread* thr = Emu.GetCPU().GetThread(thread_id);
	if(!thr) return CELL_ESRCH;

	GetCurrentPPUThread().Wait(*thr);
	return CELL_OK;
}

int sys_ppu_thread_detach(u32 thread_id)
{
	sysPrxForUser.Error("sys_ppu_thread_detach(thread_id=%d)", thread_id);

	CPUThread* thr = Emu.GetCPU().GetThread(thread_id);
	if(!thr) return CELL_ESRCH;

	if(!thr->IsJoinable())
		return CELL_EINVAL;
	thr->SetJoinable(false);

	return CELL_OK;
}

void sys_ppu_thread_get_join_state(u32 isjoinable_addr)
{
	sysPrxForUser.Warning("sys_ppu_thread_get_join_state(isjoinable_addr=0x%x)", isjoinable_addr);
	Memory.Write32(isjoinable_addr, GetCurrentPPUThread().IsJoinable());
}

int sys_ppu_thread_set_priority(u32 thread_id, int prio)
{
	sysPrxForUser.Warning("sys_ppu_thread_set_priority(thread_id=%d, prio=%d)", thread_id, prio);

	CPUThread* thr = Emu.GetCPU().GetThread(thread_id);
	if(!thr) return CELL_ESRCH;

	thr->SetPrio(prio);

	return CELL_OK;
}

int sys_ppu_thread_get_priority(u32 thread_id, u32 prio_addr)
{
	sysPrxForUser.Log("sys_ppu_thread_get_priority(thread_id=%d, prio_addr=0x%x)", thread_id, prio_addr);

	CPUThread* thr = Emu.GetCPU().GetThread(thread_id);
	if(!thr) return CELL_ESRCH;
	if(!Memory.IsGoodAddr(prio_addr)) return CELL_EFAULT;

	Memory.Write32(prio_addr, thr->GetPrio());

	return CELL_OK;
}

int sys_ppu_thread_get_stack_information(u32 info_addr)
{
	sysPrxForUser.Log("sys_ppu_thread_get_stack_information(info_addr=0x%x)", info_addr);

	if(!Memory.IsGoodAddr(info_addr)) return CELL_EFAULT;

	declCPU();

	Memory.Write32(info_addr,   CPU.GetStackAddr());
	Memory.Write32(info_addr+4, CPU.GetStackSize());

	return CELL_OK;
}

int sys_ppu_thread_stop(u32 thread_id)
{
	sysPrxForUser.Warning("sys_ppu_thread_stop(thread_id=%d)", thread_id);

	CPUThread* thr = Emu.GetCPU().GetThread(thread_id);
	if(!thr) return CELL_ESRCH;

	thr->Stop();

	return CELL_OK;
}

int sys_ppu_thread_restart(u32 thread_id)
{
	sysPrxForUser.Warning("sys_ppu_thread_restart(thread_id=%d)", thread_id);

	CPUThread* thr = Emu.GetCPU().GetThread(thread_id);
	if(!thr) return CELL_ESRCH;

	thr->Stop();
	thr->Run();

	return CELL_OK;
}

int sys_ppu_thread_create(u32 thread_id_addr, u32 entry, u32 arg, int prio, u32 stacksize, u64 flags, u32 threadname_addr)
{
	sysPrxForUser.Log("sys_ppu_thread_create(thread_id_addr=0x%x, entry=0x%x, arg=0x%x, prio=%d, stacksize=0x%x, flags=0x%llx, threadname_addr=0x%x('%s'))",
		thread_id_addr, entry, arg, prio, stacksize, flags, threadname_addr, Memory.ReadString(threadname_addr));

	if(!Memory.IsGoodAddr(entry) || !Memory.IsGoodAddr(thread_id_addr) || !Memory.IsGoodAddr(threadname_addr))
	{
		return CELL_EFAULT;
	}

	CPUThread& new_thread = Emu.GetCPU().AddThread(CPU_THREAD_PPU);

	Memory.Write32(thread_id_addr, new_thread.GetId());
	new_thread.SetEntry(entry);
	new_thread.SetArg(0, arg);
	new_thread.SetPrio(prio);
	new_thread.SetStackSize(stacksize);
	//new_thread.flags = flags;
	new_thread.SetName(Memory.ReadString(threadname_addr));
	new_thread.Run();
	new_thread.Exec();

	return CELL_OK;
}

void sys_ppu_thread_once(u32 once_ctrl_addr, u32 entry)
{
	sysPrxForUser.Warning("sys_ppu_thread_once(once_ctrl_addr=0x%x, entry=0x%x)", once_ctrl_addr, entry);

	if(Memory.IsGoodAddr(once_ctrl_addr, 4) && Memory.Read32(once_ctrl_addr) == SYS_PPU_THREAD_ONCE_INIT)
	{
		Memory.Write32(once_ctrl_addr, SYS_PPU_THREAD_DONE_INIT);

		CPUThread& new_thread = Emu.GetCPU().AddThread(CPU_THREAD_PPU);
		new_thread.SetEntry(entry);
		new_thread.Run();
		new_thread.Exec();

		GetCurrentPPUThread().Wait(new_thread);
	}
}

int sys_ppu_thread_get_id(const u32 id_addr)
{
	sysPrxForUser.Log("sys_ppu_thread_get_id(id_addr=0x%x)", id_addr);

	Memory.Write32(id_addr, GetCurrentPPUThread().GetId());
	return CELL_OK;
}
