/*
 * Process Hacker Extra Plugins -
 *   Average CPU Plugin
 *
 * Copyright (C) 2011 wj32
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker 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 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker 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 Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <phdk.h>
#include "resource.h"

#define COLUMN_ID_AVGCPU10 1
#define COLUMN_ID_AVGCPU60 2
#define HISTORY_SIZE 60

typedef struct _PROCESS_EXTENSION
{
    LIST_ENTRY ListEntry;
    PPH_PROCESS_ITEM ProcessItem;

    FLOAT CpuHistory[HISTORY_SIZE];
    ULONG CpuHistoryCount;
    ULONG CpuHistoryPosition;

    FLOAT Avg10CpuUsage;
    FLOAT Avg60CpuUsage;
    WCHAR Avg10CpuUsageText[PH_INT32_STR_LEN_1];
    WCHAR Avg60CpuUsageText[PH_INT32_STR_LEN_1];
} PROCESS_EXTENSION, *PPROCESS_EXTENSION;

PPH_PLUGIN PluginInstance;
PH_CALLBACK_REGISTRATION TreeNewMessageCallbackRegistration;
PH_CALLBACK_REGISTRATION ProcessTreeNewInitializingCallbackRegistration;
PH_CALLBACK_REGISTRATION ProcessAddedCallbackRegistration;
PH_CALLBACK_REGISTRATION ProcessRemovedCallbackRegistration;
PH_CALLBACK_REGISTRATION ProcessesUpdatedCallbackRegistration;

LIST_ENTRY ProcessListHead = { &ProcessListHead, &ProcessListHead };

VOID TreeNewMessageCallback(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    PPH_PLUGIN_TREENEW_MESSAGE message = Parameter;

    switch (message->Message)
    {
    case TreeNewGetCellText:
        {
            PPH_TREENEW_GET_CELL_TEXT getCellText = message->Parameter1;
            PPH_PROCESS_NODE node;
            PPROCESS_EXTENSION extension;

            node = (PPH_PROCESS_NODE)getCellText->Node;
            extension = PhPluginGetObjectExtension(PluginInstance, node->ProcessItem, EmProcessItemType);

            switch (message->SubId)
            {
            case COLUMN_ID_AVGCPU10:
            case COLUMN_ID_AVGCPU60:
                {
                    FLOAT cpuUsage;
                    PWCHAR buffer;

                    if (message->SubId == COLUMN_ID_AVGCPU10)
                    {
                        cpuUsage = extension->Avg10CpuUsage * 100;
                        buffer = extension->Avg10CpuUsageText;
                    }
                    else
                    {
                        cpuUsage = extension->Avg60CpuUsage * 100;
                        buffer = extension->Avg60CpuUsageText;
                    }

                    if (cpuUsage >= 0.01)
                    {
                        PH_FORMAT format;
                        SIZE_T returnLength;

                        PhInitFormatF(&format, cpuUsage, 2);

                        if (PhFormatToBuffer(&format, 1, buffer, PH_INT32_STR_LEN_1 * sizeof(WCHAR), &returnLength))
                        {
                            getCellText->Text.Buffer = buffer;
                            getCellText->Text.Length = (USHORT)(returnLength - sizeof(WCHAR)); // minus null terminator
                        }
                    }
                    else if (cpuUsage != 0 && PhGetIntegerSetting(L"ShowCpuBelow001"))
                    {
                        PhInitializeStringRef(&getCellText->Text, L"< 0.01");
                    }
                }
                break;
            }
        }
        break;
    }
}

LONG NTAPI AvgCpuSortFunction(
    _In_ PVOID Node1,
    _In_ PVOID Node2,
    _In_ ULONG SubId,
    _In_ PVOID Context
    )
{
    PPH_PROCESS_NODE node1 = Node1;
    PPH_PROCESS_NODE node2 = Node2;
    PPROCESS_EXTENSION extension1 = PhPluginGetObjectExtension(PluginInstance, node1->ProcessItem, EmProcessItemType);
    PPROCESS_EXTENSION extension2 = PhPluginGetObjectExtension(PluginInstance, node2->ProcessItem, EmProcessItemType);

    switch (SubId)
    {
    case COLUMN_ID_AVGCPU10:
        return singlecmp(extension1->Avg10CpuUsage, extension2->Avg10CpuUsage);
    case COLUMN_ID_AVGCPU60:
        return singlecmp(extension1->Avg60CpuUsage, extension2->Avg60CpuUsage);
    }

    return 0;
}

VOID ProcessTreeNewInitializingCallback(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    PPH_PLUGIN_TREENEW_INFORMATION info = Parameter;
    PH_TREENEW_COLUMN column;

    memset(&column, 0, sizeof(PH_TREENEW_COLUMN));
    column.SortDescending = TRUE;
    column.Text = L"CPU Average (10)";
    column.Width = 45;
    column.Alignment = PH_ALIGN_RIGHT;
    column.TextFlags = DT_RIGHT;
    PhPluginAddTreeNewColumn(PluginInstance, info->CmData, &column, COLUMN_ID_AVGCPU10, NULL, AvgCpuSortFunction);

    column.Text = L"CPU Average (60)";
    PhPluginAddTreeNewColumn(PluginInstance, info->CmData, &column, COLUMN_ID_AVGCPU60, NULL, AvgCpuSortFunction);
}

VOID ProcessItemCreateCallback(
    _In_ PVOID Object,
    _In_ PH_EM_OBJECT_TYPE ObjectType,
    _In_ PVOID Extension
    )
{
    PPH_PROCESS_ITEM processItem = Object;
    PPROCESS_EXTENSION extension = Extension;

    memset(extension, 0, sizeof(PROCESS_EXTENSION));
    extension->ProcessItem = processItem;
}

VOID ProcessAddedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    PPH_PROCESS_ITEM processItem = Parameter;
    PPROCESS_EXTENSION extension = PhPluginGetObjectExtension(PluginInstance, processItem, EmProcessItemType);

    InsertTailList(&ProcessListHead, &extension->ListEntry);
}

VOID ProcessRemovedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    PPH_PROCESS_ITEM processItem = Parameter;
    PPROCESS_EXTENSION extension = PhPluginGetObjectExtension(PluginInstance, processItem, EmProcessItemType);

    RemoveEntryList(&extension->ListEntry);
}

VOID ProcessesUpdatedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    static ULONG runCount = 0;

    if (runCount != 0)
    {
        PLIST_ENTRY listEntry;

        listEntry = ProcessListHead.Flink;

        while (listEntry != &ProcessListHead)
        {
            PPROCESS_EXTENSION extension = CONTAINING_RECORD(listEntry, PROCESS_EXTENSION, ListEntry);
            PPH_PROCESS_ITEM processItem = extension->ProcessItem;
            FLOAT sum;
            ULONG i;
            ULONG total;
            ULONG count;

            if (extension->CpuHistoryPosition != 0)
                extension->CpuHistoryPosition--;
            else
                extension->CpuHistoryPosition = HISTORY_SIZE - 1;

            extension->CpuHistory[extension->CpuHistoryPosition] = processItem->CpuUsage;

            if (extension->CpuHistoryCount < HISTORY_SIZE)
                extension->CpuHistoryCount++;

            // Calculate the 10 interval average.

            sum = 0;
            i = extension->CpuHistoryPosition;
            total = 10;

            if (total > extension->CpuHistoryCount)
                total = extension->CpuHistoryCount;

            count = total;

            do
            {
                sum += extension->CpuHistory[i];
                i++;

                if (i == HISTORY_SIZE)
                    i = 0;
            } while (--count != 0);

            extension->Avg10CpuUsage = sum / total;

            // Calculate the 60 interval average.

            if (extension->CpuHistoryCount > 10)
            {
                total = 50;

                if (total > extension->CpuHistoryCount - 10)
                    total = extension->CpuHistoryCount - 10;

                count = total;

                do
                {
                    sum += extension->CpuHistory[i];
                    i++;

                    if (i == HISTORY_SIZE)
                        i = 0;
                } while (--count != 0);

                extension->Avg60CpuUsage = sum / (total + 10);
            }
            else
            {
                // Not enough samples.
                extension->Avg60CpuUsage = extension->Avg10CpuUsage;
            }

            listEntry = listEntry->Flink;
        }
    }

    runCount++;
}

LOGICAL DllMain(
    _In_ HINSTANCE Instance,
    _In_ ULONG Reason,
    _Reserved_ PVOID Reserved
    )
{
    if (Reason == DLL_PROCESS_ATTACH)
    {
        PPH_PLUGIN_INFORMATION info;

        PluginInstance = PhRegisterPlugin(L"wj32.AvgCpuPlugin", Instance, &info);

        if (!PluginInstance)
            return FALSE;

        info->DisplayName = L"Average CPU";
        info->Description = L"Adds a column to display average CPU times.";
        info->Author = L"wj32";

        PhRegisterCallback(PhGetPluginCallback(PluginInstance, PluginCallbackTreeNewMessage),
            TreeNewMessageCallback, NULL, &TreeNewMessageCallbackRegistration);
        PhRegisterCallback(PhGetGeneralCallback(GeneralCallbackProcessTreeNewInitializing),
            ProcessTreeNewInitializingCallback, NULL, &ProcessTreeNewInitializingCallbackRegistration);
        PhRegisterCallback(&PhProcessAddedEvent, ProcessAddedHandler, NULL, &ProcessAddedCallbackRegistration);
        PhRegisterCallback(&PhProcessRemovedEvent, ProcessRemovedHandler, NULL, &ProcessRemovedCallbackRegistration);
        PhRegisterCallback(&PhProcessesUpdatedEvent, ProcessesUpdatedHandler, NULL, &ProcessesUpdatedCallbackRegistration);

        PhPluginSetObjectExtension(PluginInstance, EmProcessItemType, sizeof(PROCESS_EXTENSION),
            ProcessItemCreateCallback, NULL);
    }

    return TRUE;
}
