// Copyright (c) 2012- PPSSPP Project.

// 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, version 2.0 or later versions.

// 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#pragma once

#include <map>
#include <string>

#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/Swap.h"

class PointerWrap;

// If you add to this, make sure to check KernelObjectPool::CreateByIDType().
enum TMIDType : int {
	SCE_KERNEL_TMID_Thread             = 1,
	SCE_KERNEL_TMID_Semaphore          = 2,
	SCE_KERNEL_TMID_EventFlag          = 3,
	SCE_KERNEL_TMID_Mbox               = 4,
	SCE_KERNEL_TMID_Vpl                = 5,
	SCE_KERNEL_TMID_Fpl                = 6,
	SCE_KERNEL_TMID_Mpipe              = 7,
	SCE_KERNEL_TMID_Callback           = 8,
	SCE_KERNEL_TMID_ThreadEventHandler = 9,
	SCE_KERNEL_TMID_Alarm              = 10,
	SCE_KERNEL_TMID_VTimer             = 11,
	SCE_KERNEL_TMID_Mutex              = 12,
	SCE_KERNEL_TMID_LwMutex            = 13,
	SCE_KERNEL_TMID_Tlspl              = 14,
	SCE_KERNEL_TMID_SleepThread        = 64,
	SCE_KERNEL_TMID_DelayThread        = 65,
	SCE_KERNEL_TMID_SuspendThread      = 66,
	SCE_KERNEL_TMID_DormantThread      = 67,
	// This is kept for old savestates.  Not the real value.
	SCE_KERNEL_TMID_Tlspl_v0           = 0x1001,

	// Not official, but need ids for save states.
	PPSSPP_KERNEL_TMID_Module          = 0x100001,
	PPSSPP_KERNEL_TMID_PMB             = 0x100002,
	PPSSPP_KERNEL_TMID_File            = 0x100003,
	PPSSPP_KERNEL_TMID_DirList         = 0x100004,
	PPSSPP_KERNEL_TMID_Heap            = 0x100005,
};

typedef int SceUID;
typedef unsigned int SceSize;
typedef int SceSSize;
typedef unsigned char SceUChar;
typedef unsigned int SceUInt;
typedef int SceMode;
typedef s64 SceOff;
typedef u64 SceIores;

typedef s32_le SceUID_le;
typedef u32_le SceSize_le;
typedef s32_le SceSSize_le;
typedef u32_le SceUInt_le;
typedef s32_le SceMode_le;
typedef s64_le SceOff_le;
typedef s64_le SceIores_le;

struct SceKernelLoadExecParam
{
	SceSize_le size;    // Size of the structure
	SceSize_le args;    // Size of the arg string
	u32_le argp;      // Pointer to the arg string
	u32_le keyp; // Encryption key? Not yet used
};

void __KernelInit();
void __KernelShutdown();
void __KernelDoState(PointerWrap &p);
bool __KernelIsRunning();
bool __KernelLoadExec(const char *filename, SceKernelLoadExecParam *param);

// For crash reporting.
std::string __KernelStateSummary();

int sceKernelLoadExec(const char *filename, u32 paramPtr);

void sceKernelExitGame();
void sceKernelExitGameWithStatus();

u32 sceKernelDevkitVersion();

u32 sceKernelRegisterKprintfHandler();
int sceKernelRegisterDefaultExceptionHandler();

u32 sceKernelFindModuleByName(const char *name);

void sceKernelSetGPO(u32 ledAddr);
u32 sceKernelGetGPI();
int sceKernelDcacheInvalidateRange(u32 addr, int size);
int sceKernelDcacheWritebackAll();
int sceKernelDcacheWritebackRange(u32 addr, int size);
int sceKernelDcacheWritebackInvalidateRange(u32 addr, int size);
int sceKernelDcacheWritebackInvalidateAll();
int sceKernelGetThreadStackFreeSize(SceUID threadID);
u32 sceKernelIcacheInvalidateAll();
u32 sceKernelIcacheClearAll();
int sceKernelIcacheInvalidateRange(u32 addr, int size);

#define KERNELOBJECT_MAX_NAME_LENGTH 31

class KernelObjectPool;

class KernelObject {
	friend class KernelObjectPool;
	u32 uid;
public:
	virtual ~KernelObject() {}
	SceUID GetUID() const {return uid;}
	virtual const char *GetTypeName() {return "[BAD KERNEL OBJECT TYPE]";}
	virtual const char *GetName() {return "[UNKNOWN KERNEL OBJECT]";}
	virtual int GetIDType() const = 0;
	virtual void GetQuickInfo(char *ptr, int size);
	virtual void GetLongInfo(char *ptr, int bufSize) const;

	// Implement the following in all subclasses:
	// static u32 GetMissingErrorCode()
	// static int GetStaticIDType()

	virtual void DoState(PointerWrap &p) {
		_dbg_assert_msg_(false, "Unable to save state: bad kernel object.");
	}
};

// TODO: Delete the "occupied" array, rely on non-zero pool entries?
class KernelObjectPool {
public:
	KernelObjectPool();
	~KernelObjectPool() {}

	// Allocates a UID within the range and inserts the object into the map.
	SceUID Create(KernelObject *obj, int rangeBottom = initialNextID, int rangeTop = 0x7fffffff);

	void DoState(PointerWrap &p);
	static KernelObject *CreateByIDType(int type);

	template <class T>
	u32 Destroy(SceUID handle) {
		u32 error;
		if (Get<T>(handle, error)) {
			int index = handle - handleOffset;
			occupied[index] = false;
			delete pool[index];
			pool[index] = nullptr;
		}
		return error;
	};

	bool IsValid(SceUID handle) const {
		int index = handle - handleOffset;
		if (index < 0 || index >= maxCount)
			return false;
		else
			return occupied[index];
	}

	template<class T>
	bool Is(SceUID handle) const {
		int index = handle - handleOffset;
		if (index < 0 || index >= maxCount)
			return false;
		else
			return occupied[index] && pool[handle - handleOffset]->GetIDType() == T::GetStaticIDType();
	}

	template <class T>
	T* Get(SceUID handle, u32 &outError) {
		if (handle < handleOffset || handle >= handleOffset+maxCount || !occupied[handle-handleOffset]) {
			outError = T::GetMissingErrorCode();
			return nullptr;
		} else {
			// Previously we had a dynamic_cast here, but since RTTI was disabled traditionally,
			// it just acted as a static cast and everything worked. This means that we will never
			// see the Wrong type object error below, but we'll just have to live with that danger.
			T *t = static_cast<T *>(pool[handle - handleOffset]);
			if (t == nullptr || t->GetIDType() != T::GetStaticIDType()) {
				WARN_LOG(Log::sceKernel, "Kernel: Wrong object type for %d (%08x), was %s, should have been %s", handle, handle, t ? t->GetTypeName() : "null", T::GetStaticTypeName());
				outError = T::GetMissingErrorCode();
				return nullptr;
			}
			outError = 0; // SCE_KERNEL_ERROR_OK but don't want to include the header here.
			return t;
		}
	}

	// ONLY use this when you KNOW the handle is valid.
	template <class T>
	T *GetFast(SceUID handle) {
		const SceUID realHandle = handle - handleOffset;
		_dbg_assert_(realHandle >= 0 && realHandle < maxCount && occupied[realHandle]);
		return static_cast<T *>(pool[realHandle]);
	}

	template <typename T, typename F>
	void Iterate(F func) {
		int type = T::GetStaticIDType();
		for (int i = 0; i < maxCount; i++) {
			if (!occupied[i])
				continue;
			T *t = static_cast<T *>(pool[i]);
			if (t->GetIDType() == type) {
				if (!func(i + handleOffset, t))
					break;
			}
		}
	}

	int ListIDType(int type, SceUID_le *uids, int count) const {
		int total = 0;
		for (int i = 0; i < maxCount; i++) {
			if (!occupied[i]) {
				continue;
			}
			if (pool[i]->GetIDType() == type) {
				if (total < count) {
					*uids++ = pool[i]->GetUID();
				}
				++total;
			}
		}
		return total;
	}

	bool GetIDType(SceUID handle, int *type) const {
		if (handle < handleOffset || handle >= handleOffset+maxCount || !occupied[handle-handleOffset]) {
			ERROR_LOG(Log::sceKernel, "Kernel: Bad object handle %i (%08x)", handle, handle);
			return false;
		}
		KernelObject *t = pool[handle - handleOffset];
		*type = t->GetIDType();
		return true;
	}

	void List();
	void Clear();
	int GetCount() const;

	enum {
		maxCount = 4096,
		handleOffset = 0x100,
		initialNextID = 0x10
	};
private:
	KernelObject *pool[maxCount];
	bool occupied[maxCount];
	int nextID;
};

extern KernelObjectPool kernelObjects;

typedef std::pair<int, int> KernelStatsSyscall;

struct KernelStats {
	void Reset() {
		ResetFrame();
	}
	void ResetFrame() {
		msInSyscalls = 0;
		slowestSyscallTime = 0;
		slowestSyscallName = 0;
		summedMsInSyscalls.clear();
		summedSlowestSyscallTime = 0;
		summedSlowestSyscallName = 0;
	}

	double msInSyscalls;
	double slowestSyscallTime;
	const char *slowestSyscallName;
	std::map<KernelStatsSyscall, double> summedMsInSyscalls;
	double summedSlowestSyscallTime;
	const char *summedSlowestSyscallName;
};

extern KernelStats kernelStats;
extern u32 registeredExitCbId;

extern u32 g_GPOBits;
extern u32 g_GPIBits;

void Register_ThreadManForUser();
void Register_ThreadManForKernel();
void Register_LoadExecForUser();
void Register_LoadExecForKernel();
void Register_UtilsForKernel();

// returns nullptr if not found.
const char *KernelErrorToString(u32 err);
