#include "diagnostics_win.h"
#include "procinfo.h"

using std::vector;

// NtQuerySystemInformation
typedef NTSTATUS (WINAPI *tNTQSI)(
    ULONG SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
);

// OpenThread
typedef HANDLE (WINAPI *tOT)(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId);

static int get_process_information(PVOID* ppBuffer, PULONG pcbBuffer) {
    int      retval = 0;
    NTSTATUS Status = STATUS_INFO_LENGTH_MISMATCH;
    HANDLE   hHeap  = GetProcessHeap();
    HMODULE  hNTDllLib = NULL;
    tNTQSI   pNTQSI = NULL;

    hNTDllLib = GetModuleHandle("ntdll.dll");
    pNTQSI = (tNTQSI)GetProcAddress(hNTDllLib, "NtQuerySystemInformation");

    do {
        *ppBuffer = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, *pcbBuffer);
        if (ppBuffer == NULL) {
            retval = ERROR_NOT_ENOUGH_MEMORY;
        }

        Status = pNTQSI(
            SystemProcessAndThreadInformation,
            *ppBuffer,
            *pcbBuffer,
            pcbBuffer
        );

        if (Status == STATUS_INFO_LENGTH_MISMATCH) {
            HeapFree(hHeap, NULL, *ppBuffer);
            *pcbBuffer *= 2;
        } else if (!NT_SUCCESS(Status)) {
            HeapFree(hHeap, NULL, *ppBuffer);
            retval = Status;
        }
    } while (Status == STATUS_INFO_LENGTH_MISMATCH);

    return retval;
}

int get_procinfo_XP(vector<PROCINFO>& pi) {
    ULONG                   cbBuffer = 32*1024;    // 32k initial buffer
    PVOID                   pBuffer = NULL;
    PSYSTEM_PROCESSES       pProcesses = NULL;
    HMODULE                 hKernel32Lib;
    tOT                     pOT = NULL;

    hKernel32Lib = GetModuleHandle("kernel32.dll");
    pOT = (tOT) GetProcAddress( hKernel32Lib, "OpenThread" );

    get_process_information(&pBuffer, &cbBuffer);
    pProcesses = (PSYSTEM_PROCESSES)pBuffer;
    while (pProcesses) {
        PROCINFO p;
		p.id = pProcesses->ProcessId;
		p.parentid = pProcesses->InheritedFromProcessId;
        p.swap_size = pProcesses->VmCounters.PagefileUsage;
        p.working_set_size = pProcesses->VmCounters.WorkingSetSize;
        p.user_time = ((double) pProcesses->UserTime.QuadPart)/1e7;
        p.kernel_time = ((double) pProcesses->KernelTime.QuadPart)/1e7;
		p.id = pProcesses->ProcessId;
		p.parentid = pProcesses->InheritedFromProcessId;
		p.is_boinc_app = false;
        pi.push_back(p);

        if (!pProcesses->NextEntryDelta) {
            break;
        }
        pProcesses = (PSYSTEM_PROCESSES)(((LPBYTE)pProcesses) + pProcesses->NextEntryDelta);
    }

    if (pBuffer) HeapFree(GetProcessHeap(), NULL, pBuffer);
    return 0;
}
int get_procinfo_NT(vector<PROCINFO>&) {
    return 0;
}
int get_procinfo_9X(vector<PROCINFO>&) {
    return 0;
}

// get a list of all running processes.
//
int procinfo_setup(vector<PROCINFO>& pi) {
    OSVERSIONINFO osvi; 
    osvi.dwOSVersionInfoSize = sizeof(osvi);
    GetVersionEx(&osvi);

    pi.clear();

    switch(osvi.dwPlatformId) {
    case VER_PLATFORM_WIN32_WINDOWS:
        // Win95, Win98, WinME
        return get_procinfo_9X(pi);
    case VER_PLATFORM_WIN32_NT:
        switch(osvi.dwMajorVersion) {
        case 4:
            // WinNT 4.0
            return get_procinfo_NT(pi);
        case 5:
            // Win2k, WinXP, Win2k3
            return get_procinfo_XP(pi);
        case 6:
            if (osvi.dwMinorVersion == 0) {
                // WinVista
                return get_procinfo_XP(pi);
            } else {
                return get_procinfo_9X(pi);
            }
        default:
            return get_procinfo_9X(pi);
        }
    }
    return get_procinfo_9X(pi);
}

// scan the process table from the given point,
// adding in CPU time and mem usage
// 
void add_proc_totals(PROCINFO& pi, vector<PROCINFO>& piv, int pid, int start) {
	unsigned int i;
	for (i=start; i<piv.size(); i++) {
		PROCINFO& p = piv[i];
		if (p.id == pid || p.parentid == pid) {
			pi.kernel_time += p.kernel_time;
			pi.user_time += p.user_time;
			pi.swap_size += p.swap_size;
			pi.working_set_size += p.working_set_size;
			p.is_boinc_app = true;
		} 
		if (p.parentid == pid) {
			add_proc_totals(pi, piv, p.id, i+1);	// recursion - woo hoo!
		}
	}
}

// fill in the given PROCINFO (which initially is zero except for id)
// with totals from that process and all its descendants
//
void procinfo_app(PROCINFO& pi, vector<PROCINFO>& piv) {
	add_proc_totals(pi, piv, pi.id, 0);
}

void procinfo_other(PROCINFO& pi, vector<PROCINFO>& piv) {
	unsigned int i;
	memset(&pi, 0, sizeof(pi));
	for (i=0; i<piv.size(); i++) {
		PROCINFO& p = piv[i];
		if (!p.is_boinc_app) {
			pi.kernel_time += p.kernel_time;
			pi.user_time += p.user_time;
			pi.swap_size += p.swap_size;
			pi.working_set_size += p.working_set_size;
			p.is_boinc_app = true;
		}
	}
}