#include "test-tool.h"
#include "git-compat-util.h"

#if defined(GIT_WINDOWS_NATIVE)
#include "lazyload.h"
#include <winnt.h>

static int cmd_sync(void)
{
	char Buffer[MAX_PATH];
	DWORD dwRet;
	char szVolumeAccessPath[] = "\\\\.\\XXXX:";
	HANDLE hVolWrite;
	int success = 0, dos_drive_prefix;

	dwRet = GetCurrentDirectory(MAX_PATH, Buffer);
	if ((0 == dwRet) || (dwRet > MAX_PATH))
		return error("Error getting current directory");

	dos_drive_prefix = has_dos_drive_prefix(Buffer);
	if (!dos_drive_prefix)
		return error("'%s': invalid drive letter", Buffer);

	memcpy(szVolumeAccessPath, Buffer, dos_drive_prefix);
	szVolumeAccessPath[dos_drive_prefix] = '\0';

	hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	if (INVALID_HANDLE_VALUE == hVolWrite)
		return error("Unable to open volume for writing, need admin access");

	success = FlushFileBuffers(hVolWrite);
	if (!success)
		error("Unable to flush volume");

	CloseHandle(hVolWrite);

	return !success;
}

#define STATUS_SUCCESS			(0x00000000L)
#define STATUS_PRIVILEGE_NOT_HELD	(0xC0000061L)

typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemMemoryListInformation = 80,
} SYSTEM_INFORMATION_CLASS;

typedef enum _SYSTEM_MEMORY_LIST_COMMAND {
	MemoryCaptureAccessedBits,
	MemoryCaptureAndResetAccessedBits,
	MemoryEmptyWorkingSets,
	MemoryFlushModifiedList,
	MemoryPurgeStandbyList,
	MemoryPurgeLowPriorityStandbyList,
	MemoryCommandMax
} SYSTEM_MEMORY_LIST_COMMAND;

static BOOL GetPrivilege(HANDLE TokenHandle, LPCSTR lpName, int flags)
{
	BOOL bResult;
	DWORD dwBufferLength;
	LUID luid;
	TOKEN_PRIVILEGES tpPreviousState;
	TOKEN_PRIVILEGES tpNewState;

	dwBufferLength = 16;
	bResult = LookupPrivilegeValueA(0, lpName, &luid);
	if (bResult) {
		tpNewState.PrivilegeCount = 1;
		tpNewState.Privileges[0].Luid = luid;
		tpNewState.Privileges[0].Attributes = 0;
		bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpNewState,
			(DWORD)((LPBYTE)&(tpNewState.Privileges[1]) - (LPBYTE)&tpNewState),
			&tpPreviousState, &dwBufferLength);
		if (bResult) {
			tpPreviousState.PrivilegeCount = 1;
			tpPreviousState.Privileges[0].Luid = luid;
			tpPreviousState.Privileges[0].Attributes = flags != 0 ? 2 : 0;
			bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpPreviousState,
				dwBufferLength, 0, 0);
		}
	}
	return bResult;
}

static int cmd_dropcaches(void)
{
	HANDLE hProcess = GetCurrentProcess();
	HANDLE hToken;
	DECLARE_PROC_ADDR(ntdll.dll, DWORD, NTAPI, NtSetSystemInformation, INT, PVOID,
		ULONG);
	SYSTEM_MEMORY_LIST_COMMAND command;
	int status;

	if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
		return error("Can't open current process token");

	if (!GetPrivilege(hToken, "SeProfileSingleProcessPrivilege", 1))
		return error("Can't get SeProfileSingleProcessPrivilege");

	CloseHandle(hToken);

	if (!INIT_PROC_ADDR(NtSetSystemInformation))
		return error("Could not find NtSetSystemInformation() function");

	command = MemoryPurgeStandbyList;
	status = NtSetSystemInformation(
		SystemMemoryListInformation,
		&command,
		sizeof(SYSTEM_MEMORY_LIST_COMMAND)
	);
	if (status == STATUS_PRIVILEGE_NOT_HELD)
		error("Insufficient privileges to purge the standby list, need admin access");
	else if (status != STATUS_SUCCESS)
		error("Unable to execute the memory list command %d", status);

	return status;
}

#elif defined(__linux__)

static int cmd_sync(void)
{
	return system("sync");
}

static int cmd_dropcaches(void)
{
	return system("echo 3 | sudo tee /proc/sys/vm/drop_caches");
}

#elif defined(__APPLE__)

static int cmd_sync(void)
{
	return system("sync");
}

static int cmd_dropcaches(void)
{
	return system("sudo purge");
}

#else

static int cmd_sync(void)
{
	return 0;
}

static int cmd_dropcaches(void)
{
	return error("drop caches not implemented on this platform");
}

#endif

int cmd__drop_caches(int argc, const char **argv)
{
	cmd_sync();
	return cmd_dropcaches();
}
