/*
  Copyright (C) 2001 Kimmo Pekkola

  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; either version 2
  of the License, or (at your option) any later version.

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

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
/*
  $Header: /home/cvsroot/Rainmeter/Library/MeasureCPU.cpp,v 1.1.1.1 2005/07/10 18:51:06 rainy Exp $

  $Log: MeasureCPU.cpp,v $
  Revision 1.1.1.1  2005/07/10 18:51:06  rainy
  no message

  Revision 1.10  2004/06/05 10:55:54  rainy
  Too much changes to be listed in here...

  Revision 1.9  2002/04/26 18:24:16  rainy
  Modified the Update method to support disabled measures.

  Revision 1.8  2002/03/31 09:58:54  rainy
  Added some comments

  Revision 1.7  2002/01/16 16:07:00  rainy
  Fixed a bug with the CPU meter in 9x.

  Revision 1.6  2001/10/28 10:22:49  rainy
  Changed the IsNT() function return value check

  Revision 1.5  2001/09/26 16:27:15  rainy
  Changed the interfaces a bit.

  Revision 1.4  2001/09/01 13:00:10  rainy
  Slight changes in the interface. The value is now measured only once if possible.

  Revision 1.3  2001/08/19 09:15:21  rainy
  Added support for value invert.

  Revision 1.2  2001/08/12 15:46:34  Rainy
  Adjusted Update()'s interface.
  Fixed a bug that prevent more than one CPU meter.

  Revision 1.1.1.1  2001/08/11 10:58:19  Rainy
  Added to CVS.

*/
#pragma warning(disable: 4996)

#include "MeasureCPU.h"
#include "Rainmeter.h"

#define SystemBasicInformation       0
#define SystemPerformanceInformation 2
#define SystemTimeInformation        3

#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 + (double)((x).LowPart))

// ntdll!NtQuerySystemInformation (NT specific!)
//
// The function copies the system information of the
// specified type into a buffer
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// NtQuerySystemInformation(
//    IN UINT SystemInformationClass,    // information type
//    OUT PVOID SystemInformation,       // pointer to buffer
//    IN ULONG SystemInformationLength,  // buffer size in bytes
//    OUT PULONG ReturnLength OPTIONAL   // pointer to a 32-bit
//                                       // variable that receives
//                                       // the number of bytes
//                                       // written to the buffer 
// );

/*
** CMeasureCPU
**
** The constructor
**
*/
CMeasureCPU::CMeasureCPU(CMeterWindow* meterWindow) : CMeasure(meterWindow)
{
	m_CPUFromRegistry = false;
	m_MaxValue = 100.0;
	m_MinValue = 0.0;
	m_FirstTime = true;

	m_NtQuerySystemInformation = NULL;
	m_OldIdleTime.QuadPart = 0;
	m_OldSystemTime.QuadPart = 0;

	m_NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(
										  GetModuleHandle(L"ntdll"),
										 "NtQuerySystemInformation"
										 );

	GetSystemInfo(&m_SystemInfo);
}

/*
** ~CMeasureCPU
**
** The destructor
**
*/
CMeasureCPU::~CMeasureCPU()
{
	if(m_CPUFromRegistry)
	{
		// Stop the counter if it was started
		HKEY hkey;
		DWORD dwDataSize;
		DWORD dwType;
		DWORD dwDummy;

		RegOpenKeyEx(HKEY_DYN_DATA, L"PerfStats\\StopStat", 0, KEY_ALL_ACCESS, &hkey); 
		dwDataSize = sizeof(dwDummy); 
		RegQueryValueEx(hkey, L"KERNEL\\CPUUsage", NULL, &dwType, (LPBYTE)&dwDummy, &dwDataSize); 
		RegCloseKey(hkey); 
	}
}

/*
** Update
**
** Updates the current CPU utilization value. On NT the value is taken
** from the performance counters and on 9x we'll use the registry.
**
*/
bool CMeasureCPU::Update()
{
	if (!CMeasure::PreUpdate()) return false;

	if (CRainmeter::IsNT() != PLATFORM_9X)
	{
		if (m_NtQuerySystemInformation)
		{
			// This code is 'borrowed' from http://www.codepile.com/tric21.shtml
			double dbIdleTime;
			double dbSystemTime;
			LONG status;

			// get new system time
			status = m_NtQuerySystemInformation(SystemTimeInformation, &m_SysTimeInfo, sizeof(m_SysTimeInfo), 0);
			if (status != NO_ERROR) return false;

			// get new CPU's idle time
			status = m_NtQuerySystemInformation(SystemPerformanceInformation,&m_SysPerfInfo,sizeof(m_SysPerfInfo),NULL);
			if (status != NO_ERROR) return false;

			// if it's a first call - skip it
			if(!m_FirstTime)
			{
				// CurrentValue = NewValue - OldValue
				dbIdleTime = Li2Double(m_SysPerfInfo.liIdleTime) - Li2Double(m_OldIdleTime);
				dbSystemTime = Li2Double(m_SysTimeInfo.liKeSystemTime) - Li2Double(m_OldSystemTime);

				// CurrentCpuIdle = IdleTime / SystemTime
				dbIdleTime = dbIdleTime / dbSystemTime;

				// CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors
				dbIdleTime = 100.0 - dbIdleTime * 100.0 / (double)m_SystemInfo.dwNumberOfProcessors + 0.5;

				m_Value = min(dbIdleTime, 100.0);
				m_Value = max(m_Value, 0.0);
		   }

			// store new CPU's idle and system time
			m_OldIdleTime = m_SysPerfInfo.liIdleTime;
			m_OldSystemTime = m_SysTimeInfo.liKeSystemTime;
		}
	}
	else
	{
		// It's a wintendo!
		HKEY hkey;
		DWORD dwDataSize;
		DWORD dwType;
		DWORD dwCpuUsage;

		if(m_FirstTime)
		{
			RegOpenKeyEx(HKEY_DYN_DATA, L"PerfStats\\StartStat", 0, KEY_ALL_ACCESS, &hkey); 
			dwDataSize = sizeof(dwCpuUsage); 
			RegQueryValueEx(hkey, L"KERNEL\\CPUUsage", NULL, &dwType, (LPBYTE)&dwCpuUsage, &dwDataSize); 
			RegCloseKey(hkey); 
		}

		RegOpenKeyEx(HKEY_DYN_DATA, L"PerfStats\\StatData", 0, KEY_ALL_ACCESS, &hkey); 
		dwDataSize = sizeof(dwCpuUsage); 
		RegQueryValueEx(hkey, L"KERNEL\\CPUUsage", NULL, &dwType, (LPBYTE)&dwCpuUsage, &dwDataSize); 
		RegCloseKey(hkey); 

		m_Value = dwCpuUsage;
		m_CPUFromRegistry = true;
	}

	m_FirstTime = false;

	return PostUpdate();
}
