/*
 * Process Hacker Extra Plugins -
 *   Firewall Monitor
 *
 * Copyright (C) 2015 dmex
 *
 * 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 "fwmon.h"
#include "fwtabp.h"
#include "..\..\plugins\include\toolstatusintf.h"

static BOOLEAN FwTreeNewCreated = FALSE;
static HWND FwTreeNewHandle = NULL;
static ULONG FwTreeNewSortColumn = 0;
static PH_SORT_ORDER FwTreeNewSortOrder;

BOOLEAN FwEnabled;
PPH_LIST FwNodeList;
static PH_QUEUED_LOCK FwLock = PH_QUEUED_LOCK_INIT;

static PH_CALLBACK_REGISTRATION FwItemAddedRegistration;
static PH_CALLBACK_REGISTRATION FwItemModifiedRegistration;
static PH_CALLBACK_REGISTRATION FwItemRemovedRegistration;
static PH_CALLBACK_REGISTRATION FwItemsUpdatedRegistration;
static BOOLEAN FwNeedsRedraw = FALSE;

static PH_TN_FILTER_SUPPORT FilterSupport;
static PTOOLSTATUS_INTERFACE ToolStatusInterface;
static PH_CALLBACK_REGISTRATION SearchChangedRegistration;

HWND NTAPI EtpToolStatusGetTreeNewHandle(
    VOID
    );
INT_PTR CALLBACK FwTabErrorDialogProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    );

VOID InitializeFwTab(
    VOID
    )
{
    PH_ADDITIONAL_TAB_PAGE tabPage;
    PPH_ADDITIONAL_TAB_PAGE addedTabPage;
    PPH_PLUGIN toolStatusPlugin;

    if (toolStatusPlugin = PhFindPlugin(TOOLSTATUS_PLUGIN_NAME))
    {
        ToolStatusInterface = PhGetPluginInformation(toolStatusPlugin)->Interface;

        if (ToolStatusInterface->Version < TOOLSTATUS_INTERFACE_VERSION)
            ToolStatusInterface = NULL;
    }

    memset(&tabPage, 0, sizeof(PH_ADDITIONAL_TAB_PAGE));
    tabPage.Text = L"Firewall";
    tabPage.CreateFunction = FwTabCreateFunction;
    tabPage.Index = MAXINT;
    tabPage.SelectionChangedCallback = FwTabSelectionChangedCallback;
    tabPage.SaveContentCallback = FwTabSaveContentCallback;
    tabPage.FontChangedCallback = FwTabFontChangedCallback;
    addedTabPage = ProcessHacker_AddTabPage(PhMainWndHandle, &tabPage);

    if (ToolStatusInterface)
    {
        PTOOLSTATUS_TAB_INFO tabInfo;

        tabInfo = ToolStatusInterface->RegisterTabInfo(addedTabPage->Index);
        tabInfo->BannerText = L"Search Firewall";
        tabInfo->ActivateContent = FwToolStatusActivateContent;
        tabInfo->GetTreeNewHandle = FwToolStatusGetTreeNewHandle;
    }
}

HWND NTAPI FwTabCreateFunction(
    _In_ PVOID Context
    )
{
    HWND hwnd;

    if (FwEnabled)
    {
        ULONG thinRows;

        thinRows = PhGetIntegerSetting(L"ThinRows") ? TN_STYLE_THIN_ROWS : 0;
        hwnd = CreateWindow(
            PH_TREENEW_CLASSNAME,
            NULL,
            WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_BORDER | TN_STYLE_ICONS | TN_STYLE_DOUBLE_BUFFERED | thinRows,
            0,
            0,
            3,
            3,
            PhMainWndHandle,
            NULL,
            NULL,
            NULL
            );
    }
    else
    {
        return CreateDialog(
            PluginInstance->DllBase,
            MAKEINTRESOURCE(IDD_FWTABERROR),
            PhMainWndHandle,
            FwTabErrorDialogProc
            );
    }

    FwTreeNewCreated = TRUE;

    InitializeFwTreeList(hwnd);

    PhRegisterCallback(
        &FwItemAddedEvent,
        FwItemAddedHandler,
        NULL,
        &FwItemAddedRegistration
        );
    PhRegisterCallback(
        &FwItemModifiedEvent,
        FwItemModifiedHandler,
        NULL,
        &FwItemModifiedRegistration
        );
    PhRegisterCallback(
        &FwItemRemovedEvent,
        FwItemRemovedHandler,
        NULL,
        &FwItemRemovedRegistration
        );
    PhRegisterCallback(
        &FwItemsUpdatedEvent,
        FwItemsUpdatedHandler,
        NULL,
        &FwItemsUpdatedRegistration
        );

    return hwnd;
}

VOID NTAPI FwTabSelectionChangedCallback(
    _In_ PVOID Parameter1,
    _In_ PVOID Parameter2,
    _In_ PVOID Parameter3,
    _In_ PVOID Context
    )
{
    if ((BOOLEAN)Parameter1)
    {
        SetFocus(FwTreeNewHandle);
    }
}

VOID NTAPI FwTabSaveContentCallback(
    _In_ PVOID Parameter1,
    _In_ PVOID Parameter2,
    _In_ PVOID Parameter3,
    _In_ PVOID Context
    )
{
    PPH_FILE_STREAM fileStream = (PPH_FILE_STREAM)Parameter1;
    ULONG mode = PtrToUlong(Parameter2);

    WriteFwList(fileStream, mode);
}

VOID NTAPI FwTabFontChangedCallback(
    _In_ PVOID Parameter1,
    _In_ PVOID Parameter2,
    _In_ PVOID Parameter3,
    _In_ PVOID Context
    )
{
    if (FwTreeNewHandle)
        SendMessage(FwTreeNewHandle, WM_SETFONT, (WPARAM)Parameter1, TRUE);
}

VOID InitializeFwTreeList(
    _In_ HWND hwnd
    )
{
    FwTreeNewHandle = hwnd;
    PhSetControlTheme(FwTreeNewHandle, L"explorer");
    SendMessage(TreeNew_GetTooltips(FwTreeNewHandle), TTM_SETDELAYTIME, TTDT_AUTOPOP, 0x7fff);

    TreeNew_SetCallback(hwnd, FwTreeNewCallback, NULL);
    TreeNew_SetRedraw(hwnd, FALSE);

    PhAddTreeNewColumn(hwnd, FWTNC_PROCESSBASENAME, TRUE, L"Name", 100, PH_ALIGN_LEFT, MAXINT, DT_PATH_ELLIPSIS);
    PhAddTreeNewColumn(hwnd, FWTNC_PROCESSFILENAME, FALSE, L"File Path", 80, PH_ALIGN_LEFT, MAXINT, DT_PATH_ELLIPSIS);    
    PhAddTreeNewColumn(hwnd, FWTNC_DIRECTION, TRUE, L"Direction", 40, PH_ALIGN_LEFT, MAXINT, 0);
    PhAddTreeNewColumn(hwnd, FWTNC_ACTION, TRUE, L"Action", 70, PH_ALIGN_LEFT, MAXINT, 0);
    PhAddTreeNewColumn(hwnd, FWTNC_PROTOCOL, TRUE, L"Protocol", 60, PH_ALIGN_LEFT, MAXINT, 0);
    PhAddTreeNewColumnEx(hwnd, FWTNC_LOCALADDRESS, TRUE, L"Local Address", 220, PH_ALIGN_RIGHT, MAXINT, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, FWTNC_LOCALPORT, TRUE, L"Local Port", 50, PH_ALIGN_LEFT, MAXINT, DT_LEFT, TRUE);
    PhAddTreeNewColumnEx(hwnd, FWTNC_REMOTEADDRESS, TRUE, L"Remote Address", 220, PH_ALIGN_RIGHT, MAXINT, DT_RIGHT, TRUE);
    PhAddTreeNewColumnEx(hwnd, FWTNC_REMOTEPORT, TRUE, L"Remote Port", 50, PH_ALIGN_LEFT, MAXINT, DT_LEFT, TRUE);
    PhAddTreeNewColumn(hwnd, FWTNC_USER, FALSE, L"User", 120, PH_ALIGN_LEFT, MAXINT, DT_PATH_ELLIPSIS);
    PhAddTreeNewColumnEx(hwnd, FWTNC_TIME, TRUE, L"Time", 30, PH_ALIGN_LEFT, MAXINT, 0, TRUE);
    PhAddTreeNewColumn(hwnd, FWTNC_RULENAME, TRUE, L"Rule", 200, PH_ALIGN_LEFT, MAXINT, 0);
    PhAddTreeNewColumn(hwnd, FWTNC_RULEDESCRIPTION, TRUE, L"Description", 180, PH_ALIGN_LEFT, 12, 0);
    PhAddTreeNewColumn(hwnd, FWTNC_INDEX, TRUE, L"Index", 180, PH_ALIGN_LEFT, 12, 0);    

    TreeNew_SetRedraw(hwnd, TRUE);
    TreeNew_SetSort(hwnd, FWTNC_INDEX, DescendingSortOrder);

    LoadSettingsFwTreeList();
 
    PhInitializeTreeNewFilterSupport(&FilterSupport, hwnd, FwNodeList);

    if (ToolStatusInterface)
    {
        PhRegisterCallback(ToolStatusInterface->SearchChangedEvent, FwSearchChangedHandler, NULL, &SearchChangedRegistration);
        PhAddTreeNewFilter(&FilterSupport, FwSearchFilterCallback, NULL);
    }
}

VOID LoadSettingsFwTreeList(
    VOID
    )
{
    PPH_STRING settings;
    PH_INTEGER_PAIR sortSettings;

    settings = PhGetStringSetting(SETTING_NAME_FW_TREE_LIST_COLUMNS);
    PhCmLoadSettings(FwTreeNewHandle, &settings->sr);
    PhDereferenceObject(settings);

    sortSettings = PhGetIntegerPairSetting(SETTING_NAME_FW_TREE_LIST_SORT);
    //TreeNew_SetSort(FwTreeNewHandle, (ULONG)sortSettings.X, (PH_SORT_ORDER)sortSettings.Y);
}

VOID SaveSettingsFwTreeList(
    VOID
    )
{
    PPH_STRING settings;
    PH_INTEGER_PAIR sortSettings;
    ULONG sortColumn;
    PH_SORT_ORDER sortOrder;
        
    if (!FwTreeNewCreated)  
        return;

    settings = PhCmSaveSettings(FwTreeNewHandle);
    PhSetStringSetting2(SETTING_NAME_FW_TREE_LIST_COLUMNS, &settings->sr);
    PhDereferenceObject(settings);

    TreeNew_GetSort(FwTreeNewHandle, &sortColumn, &sortOrder);
    sortSettings.X = sortColumn;
    sortSettings.Y = sortOrder;
    //PhSetIntegerPairSetting(SETTING_NAME_FW_TREE_LIST_SORT, sortSettings);
}

PFW_EVENT_NODE AddFwNode(
    _In_ PFW_EVENT_ITEM FwItem
    )
{
    PFW_EVENT_NODE FwNode;

    FwNode = PhAllocate(sizeof(FW_EVENT_NODE));
    memset(FwNode, 0, sizeof(FW_EVENT_NODE));
    PhInitializeTreeNewNode(&FwNode->Node);

    FwNode->EventItem = FwItem;
    PhReferenceObject(FwItem);

    memset(FwNode->TextCache, 0, sizeof(PH_STRINGREF) * FWTNC_MAXIMUM);
    FwNode->Node.TextCache = FwNode->TextCache;
    FwNode->Node.TextCacheSize = FWTNC_MAXIMUM;

    PhAcquireQueuedLockExclusive(&FwLock);
    PhAddItemList(FwNodeList, FwNode);
    PhReleaseQueuedLockExclusive(&FwLock);
        
    if (FilterSupport.NodeList)
        FwNode->Node.Visible = PhApplyTreeNewFiltersToNode(&FilterSupport, &FwNode->Node);

    TreeNew_NodesStructured(FwTreeNewHandle);

    return FwNode;
}

VOID RemoveFwNode(
    _In_ PFW_EVENT_NODE FwNode
    )
{
    ULONG index;

    PhAcquireQueuedLockExclusive(&FwLock);

    // Remove from the hashtable/list and cleanup.
    if ((index = PhFindItemList(FwNodeList, FwNode)) != -1)
        PhRemoveItemList(FwNodeList, index);
    
    PhReleaseQueuedLockExclusive(&FwLock);
        
    if (FwNode->TooltipText)
        PhDereferenceObject(FwNode->TooltipText);

    if (FwNode->EventItem->TimeString)
        PhDereferenceObject(FwNode->EventItem->TimeString);
    if (FwNode->EventItem->UserNameString)
        PhDereferenceObject(FwNode->EventItem->UserNameString);
    if (FwNode->EventItem->ProcessNameString)
        PhDereferenceObject(FwNode->EventItem->ProcessNameString);
    if (FwNode->EventItem->ProcessBaseString)
        PhDereferenceObject(FwNode->EventItem->ProcessBaseString);
    if (FwNode->EventItem->LocalPortString)
        PhDereferenceObject(FwNode->EventItem->LocalPortString);
    if (FwNode->EventItem->LocalAddressString)
        PhDereferenceObject(FwNode->EventItem->LocalAddressString);
    if (FwNode->EventItem->RemotePortString)
        PhDereferenceObject(FwNode->EventItem->RemotePortString);
    if (FwNode->EventItem->RemoteAddressString)
        PhDereferenceObject(FwNode->EventItem->RemoteAddressString);
    if (FwNode->EventItem->FwRuleNameString)
        PhDereferenceObject(FwNode->EventItem->FwRuleNameString);
    if (FwNode->EventItem->FwRuleDescriptionString)
        PhDereferenceObject(FwNode->EventItem->FwRuleDescriptionString);
    if (FwNode->EventItem->FwRuleLayerNameString)
        PhDereferenceObject(FwNode->EventItem->FwRuleLayerNameString);
    if (FwNode->EventItem->FwRuleLayerDescriptionString)
        PhDereferenceObject(FwNode->EventItem->FwRuleLayerDescriptionString);

    if (FwNode->EventItem->IndexString)
        PhDereferenceObject(FwNode->EventItem->IndexString);

    //if (FwNode->EventItem->Icon)
    //    DestroyIcon(FwNode->EventItem->Icon);

    PhDereferenceObject(FwNode->EventItem);
    PhFree(FwNode);

    TreeNew_NodesStructured(FwTreeNewHandle);
}

VOID UpdateFwNode(
    _In_ PFW_EVENT_NODE FwNode
    )
{
    memset(FwNode->TextCache, 0, sizeof(PH_STRINGREF) * FWTNC_MAXIMUM);

    PhInvalidateTreeNewNode(&FwNode->Node, TN_CACHE_ICON);
    TreeNew_NodesStructured(FwTreeNewHandle);
}



#define SORT_FUNCTION(Column) FwTreeNewCompare##Column
#define BEGIN_SORT_FUNCTION(Column) static int __cdecl FwTreeNewCompare##Column( \
    _In_ const void *_elem1, \
    _In_ const void *_elem2 \
    ) \
{ \
    PFW_EVENT_NODE node1 = *(PFW_EVENT_NODE*)_elem1; \
    PFW_EVENT_NODE node2 = *(PFW_EVENT_NODE*)_elem2; \
    PFW_EVENT_ITEM fwItem1 = node1->EventItem; \
    PFW_EVENT_ITEM fwItem2 = node2->EventItem; \
    int sortResult = 0;

#define END_SORT_FUNCTION \
    if (sortResult == 0) \
        sortResult = uintcmp(fwItem1->Index, fwItem2->Index); \
    \
    return PhModifySort(sortResult, FwTreeNewSortOrder); \
}

BEGIN_SORT_FUNCTION(Name)
{
    sortResult = PhCompareStringWithNull(fwItem1->ProcessBaseString, fwItem2->ProcessBaseString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(FilePath)
{
    sortResult = PhCompareStringWithNull(fwItem1->ProcessNameString, fwItem2->ProcessNameString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Direction)
{
    sortResult = PhCompareStringRef(&fwItem1->DirectionString, &fwItem2->DirectionString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Action)
{
    sortResult = PhCompareStringRef(&fwItem1->FwRuleActionString, &fwItem2->FwRuleActionString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Protocol)
{
    sortResult = PhCompareStringRef(&fwItem1->ProtocalString, &fwItem2->ProtocalString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(LocalAddress)
{
    sortResult = PhCompareStringWithNull(fwItem1->LocalAddressString, fwItem2->LocalAddressString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(LocalPort)
{
    sortResult = PhCompareStringWithNull(fwItem1->LocalPortString, fwItem2->LocalPortString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(RemoteAddress)
{
    sortResult = PhCompareStringWithNull(fwItem1->RemoteAddressString, fwItem2->RemoteAddressString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(RemotePort)
{
    sortResult = PhCompareStringWithNull(fwItem1->RemotePortString, fwItem2->RemotePortString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(User)
{
    sortResult = PhCompareStringWithNull(fwItem1->UserNameString, fwItem2->UserNameString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Time)
{
    sortResult = uint64cmp(fwItem1->AddedTime.QuadPart, fwItem2->AddedTime.QuadPart);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Rule)
{
    sortResult = PhCompareStringWithNull(fwItem1->FwRuleNameString, fwItem2->FwRuleNameString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Description)
{
    sortResult = PhCompareStringWithNull(fwItem1->FwRuleDescriptionString, fwItem2->FwRuleDescriptionString, TRUE);
}
END_SORT_FUNCTION

BEGIN_SORT_FUNCTION(Index)
{
    sortResult = uintcmp(fwItem1->Index, fwItem2->Index);
}
END_SORT_FUNCTION

BOOLEAN NTAPI FwTreeNewCallback(
    _In_ HWND hwnd,
    _In_ PH_TREENEW_MESSAGE Message,
    _In_opt_ PVOID Parameter1,
    _In_opt_ PVOID Parameter2,
    _In_opt_ PVOID Context
    )
{
    PFW_EVENT_NODE node;

    switch (Message)
    {
    case TreeNewGetChildren:
        {
            PPH_TREENEW_GET_CHILDREN getChildren = Parameter1;

            if (!getChildren->Node)
            {
                static PVOID sortFunctions[] =
                {
                    SORT_FUNCTION(Name),
                    SORT_FUNCTION(FilePath),
                    SORT_FUNCTION(Direction),
                    SORT_FUNCTION(Action),
                    SORT_FUNCTION(Protocol),
                    SORT_FUNCTION(LocalAddress),
                    SORT_FUNCTION(LocalPort),
                    SORT_FUNCTION(RemoteAddress),
                    SORT_FUNCTION(RemotePort),
                    SORT_FUNCTION(User),
                    SORT_FUNCTION(Time),
                    SORT_FUNCTION(Rule),
                    SORT_FUNCTION(Description),
                    SORT_FUNCTION(Index)
                };
                int (__cdecl *sortFunction)(const void *, const void *);

                if (FwTreeNewSortColumn < FWTNC_MAXIMUM)
                    sortFunction = sortFunctions[FwTreeNewSortColumn];
                else
                    sortFunction = NULL;

                if (sortFunction)
                {
                    qsort(FwNodeList->Items, FwNodeList->Count, sizeof(PVOID), sortFunction);
                }

                getChildren->Children = (PPH_TREENEW_NODE *)FwNodeList->Items;
                getChildren->NumberOfChildren = FwNodeList->Count;
            }
        }
        return TRUE;
    case TreeNewIsLeaf:
        {
            PPH_TREENEW_IS_LEAF isLeaf = (PPH_TREENEW_IS_LEAF)Parameter1;

            isLeaf->IsLeaf = TRUE;
        }
        return TRUE;
    case TreeNewGetCellText:
        {
            PPH_TREENEW_GET_CELL_TEXT getCellText = (PPH_TREENEW_GET_CELL_TEXT)Parameter1;
            PFW_EVENT_ITEM fwItem;

            node = (PFW_EVENT_NODE)getCellText->Node;
            fwItem = node->EventItem;

            switch (getCellText->Id)
            {            
            case FWTNC_TIME:
                getCellText->Text = PhGetStringRef(node->EventItem->TimeString);
                break;
            case FWTNC_ACTION:
                getCellText->Text = node->EventItem->FwRuleActionString;
                break;
            case FWTNC_RULENAME:
                getCellText->Text = PhGetStringRef(node->EventItem->FwRuleNameString);
                break; 
            case FWTNC_RULEDESCRIPTION:
                getCellText->Text = PhGetStringRef(node->EventItem->FwRuleDescriptionString);
                break;
            case FWTNC_PROCESSBASENAME:
                getCellText->Text = PhGetStringRef(node->EventItem->ProcessBaseString);
                break;
            case FWTNC_PROCESSFILENAME:
                getCellText->Text = PhGetStringRef(node->EventItem->ProcessNameString);
                break;
            case FWTNC_USER:
                getCellText->Text = PhGetStringRef(node->EventItem->UserNameString);
                break;
            case FWTNC_LOCALADDRESS:
                getCellText->Text = PhGetStringRef(node->EventItem->LocalAddressString);
                break;
            case FWTNC_LOCALPORT:
                getCellText->Text = PhGetStringRef(node->EventItem->LocalPortString);
                break;
            case FWTNC_REMOTEADDRESS:
                getCellText->Text = PhGetStringRef(node->EventItem->RemoteAddressString);
                break;
            case FWTNC_REMOTEPORT:
                getCellText->Text = PhGetStringRef(node->EventItem->RemotePortString);
                break;
            case FWTNC_PROTOCOL:
                getCellText->Text = node->EventItem->ProtocalString;
                break;
            case FWTNC_DIRECTION:
                getCellText->Text = node->EventItem->DirectionString;
                break;
            case FWTNC_INDEX:
                getCellText->Text = PhGetStringRef(node->EventItem->IndexString);
                break;
            default:
                return FALSE;
            }

            getCellText->Flags = TN_CACHE;
        }
        return TRUE;
    //case TreeNewGetNodeIcon:
    //    {
    //        PPH_TREENEW_GET_NODE_ICON getNodeIcon = (PPH_TREENEW_GET_NODE_ICON)Parameter1;
    //        node = (PFW_EVENT_NODE)getNodeIcon->Node;
    //
    //        if (node->EventItem->Icon)
    //        {
    //            getNodeIcon->Icon = node->EventItem->Icon;
    //        }
    //        else
    //        {
    //            PhGetStockApplicationIcon(&getNodeIcon->Icon, NULL);
    //        }
    //
    //        getNodeIcon->Flags = TN_CACHE;
    //    }
    //    return TRUE;
    case TreeNewGetCellTooltip:
        {
            PPH_TREENEW_GET_CELL_TOOLTIP getCellTooltip = (PPH_TREENEW_GET_CELL_TOOLTIP)Parameter1;
            node = (PFW_EVENT_NODE)getCellTooltip->Node;

            if (getCellTooltip->Column->Id != 0)
                return FALSE;

            //if (!node->TooltipText)
            //{
                // TODO
            //}
        }
        return TRUE;
    case TreeNewSortChanged:
        {
            TreeNew_GetSort(hwnd, &FwTreeNewSortColumn, &FwTreeNewSortOrder);
            // Force a rebuild to sort the items.
            TreeNew_NodesStructured(hwnd);
        }
        return TRUE;
    case TreeNewKeyDown:
        {
            PPH_TREENEW_KEY_EVENT keyEvent = Parameter1;

            switch (keyEvent->VirtualKey)
            {
            case 'C':
                if (GetKeyState(VK_CONTROL) < 0)
                    HandleFwCommand(ID_EVENT_COPY);
                break;
            case 'A':
                TreeNew_SelectRange(FwTreeNewHandle, 0, -1);
                break;
            case VK_RETURN:
                //EtHandleDiskCommand(ID_EVENT_?);
                break;
            }
        }
        return TRUE;
    case TreeNewHeaderRightClick:
        {
            PH_TN_COLUMN_MENU_DATA data;

            data.TreeNewHandle = hwnd;
            data.MouseEvent = Parameter1;
            data.DefaultSortColumn = 0;
            data.DefaultSortOrder = AscendingSortOrder;
            PhInitializeTreeNewColumnMenu(&data);

            data.Selection = PhShowEMenu(data.Menu, hwnd, PH_EMENU_SHOW_LEFTRIGHT,
                PH_ALIGN_LEFT | PH_ALIGN_TOP, data.MouseEvent->ScreenLocation.x, data.MouseEvent->ScreenLocation.y);
            PhHandleTreeNewColumnMenu(&data);
            PhDeleteTreeNewColumnMenu(&data);
        }
        return TRUE;
    case TreeNewLeftDoubleClick:
        {
            PPH_TREENEW_MOUSE_EVENT mouseEvent = (PPH_TREENEW_MOUSE_EVENT)Parameter1;
            node = (PFW_EVENT_NODE)mouseEvent->Node;

            //PhCreateThread(0, ShowFwRuleProperties, node);
        }
        return TRUE;
    case TreeNewContextMenu:
        {
            PPH_TREENEW_MOUSE_EVENT mouseEvent = (PPH_TREENEW_MOUSE_EVENT)Parameter1;

            ShowFwContextMenu(mouseEvent->Location);
        }
        return TRUE;
    case TreeNewDestroying:
        return TRUE;
    }

    return FALSE;
}

PFW_EVENT_NODE GetSelectedFwItem(
    VOID
    )
{
    PFW_EVENT_NODE FwItem = NULL;

    for (ULONG i = 0; i < FwNodeList->Count; i++)
    {
        PFW_EVENT_NODE node = (PFW_EVENT_NODE)FwNodeList->Items[i];

        if (node->Node.Selected)
        {
            FwItem = node;
            break;
        }
    }

    return FwItem;
}

VOID GetSelectedFwItems(
    _Out_ PFW_EVENT_NODE **FwItems,
    _Out_ PULONG NumberOfFwItems
    )
{
    PPH_LIST list = PhCreateList(2);

    for (ULONG i = 0; i < FwNodeList->Count; i++)
    {
        PFW_EVENT_NODE node = (PFW_EVENT_NODE)FwNodeList->Items[i];

        if (node->Node.Selected)
        {
            PhAddItemList(list, node->EventItem);
        }
    }

    *FwItems = PhAllocateCopy(list->Items, sizeof(PVOID) * list->Count);
    *NumberOfFwItems = list->Count;

    PhDereferenceObject(list);
}

VOID DeselectAllFwNodes(
    VOID
    )
{
    TreeNew_DeselectRange(FwTreeNewHandle, 0, -1);
}

VOID SelectAndEnsureVisibleFwNode(
    _In_ PFW_EVENT_NODE FwNode
    )
{
    DeselectAllFwNodes();

    if (!FwNode->Node.Visible)
        return;

    TreeNew_SetFocusNode(FwTreeNewHandle, &FwNode->Node);
    TreeNew_SetMarkNode(FwTreeNewHandle, &FwNode->Node);
    TreeNew_SelectRange(FwTreeNewHandle, FwNode->Node.Index, FwNode->Node.Index);
    TreeNew_EnsureVisible(FwTreeNewHandle, &FwNode->Node);
}

VOID CopyFwList(
    VOID
    )
{
    PPH_STRING text;

    text = PhGetTreeNewText(FwTreeNewHandle, 0);
    PhSetClipboardString(FwTreeNewHandle, &text->sr);
    PhDereferenceObject(text);
}

VOID WriteFwList(
    __inout PPH_FILE_STREAM FileStream,
    _In_ ULONG Mode
    )
{
    PPH_LIST lines = PhGetGenericTreeNewLines(FwTreeNewHandle, Mode);

    for (ULONG i = 0; i < lines->Count; i++)
    {
        PPH_STRING line;

        line = lines->Items[i];
        
        PhWriteStringAsUtf8FileStream(FileStream, &line->sr);
        PhDereferenceObject(line);
        PhWriteStringAsUtf8FileStream2(FileStream, L"\r\n");
    }

    PhDereferenceObject(lines);
}

VOID HandleFwCommand(
    _In_ ULONG Id
    )
{
    switch (Id)
    {
    case ID_FW_PROPERTIES:
        {
            PFW_EVENT_NODE fwItem = GetSelectedFwItem();
            //PhCreateThread(0, ShowFwRuleProperties, fwItem);
        }
        break;
    }
}

VOID InitializeFwMenu(
    _In_ PPH_EMENU Menu,
    _In_ PFW_EVENT_NODE* FwItems,
    _In_ ULONG NumberOfFwItems
    )
{
    //PPH_EMENU_ITEM item;

    if (NumberOfFwItems == 0)
    {
        PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED);
    }
    else if (NumberOfFwItems == 1)
    {
        // Stuff
        //item = PhFindEMenuItem(Menu, 0, L"?", 0);

        // Stuff
        //item->Flags |= 0;
    }
    else
    {
        PhSetFlagsAllEMenuItems(Menu, PH_EMENU_DISABLED, PH_EMENU_DISABLED);
        PhEnableEMenuItem(Menu, ID_EVENT_COPY, TRUE);
    }
}

VOID ShowFwContextMenu(
    _In_ POINT Location
    )
{
    PFW_EVENT_NODE* fwItems;
    ULONG numberOfFwItems;

    GetSelectedFwItems(&fwItems, &numberOfFwItems);

    if (numberOfFwItems != 0)
    {
        PPH_EMENU menu;
        PPH_EMENU_ITEM item;

        menu = PhCreateEMenu();

        PhLoadResourceEMenuItem(
            menu, 
            PluginInstance->DllBase,
            MAKEINTRESOURCE(IDR_FW), 
            0
            );

        //PhSetFlagsEMenuItem(menu, ID_EVENT_?, PH_EMENU_DEFAULT, PH_EMENU_DEFAULT);
        InitializeFwMenu(menu, fwItems, numberOfFwItems);

        item = PhShowEMenu(
            menu,
            PhMainWndHandle,
            PH_EMENU_SHOW_LEFTRIGHT,
            PH_ALIGN_LEFT | PH_ALIGN_TOP,
            Location.x,
            Location.y
            );

        if (item)
        {
            HandleFwCommand(item->Id);
        }

        PhDestroyEMenu(menu);
    }

    PhFree(fwItems);
}

static VOID NTAPI FwItemAddedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    PFW_EVENT_ITEM fwItem = (PFW_EVENT_ITEM)Parameter;

    PhReferenceObject(fwItem);
    ProcessHacker_Invoke(PhMainWndHandle, OnFwItemAdded, fwItem);
}

static VOID NTAPI FwItemModifiedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    ProcessHacker_Invoke(PhMainWndHandle, OnFwItemModified, (PFW_EVENT_ITEM)Parameter);
}

static VOID NTAPI FwItemRemovedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    ProcessHacker_Invoke(PhMainWndHandle, OnFwItemRemoved, (PFW_EVENT_ITEM)Parameter);
}

static VOID NTAPI FwItemsUpdatedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    ProcessHacker_Invoke(PhMainWndHandle, OnFwItemsUpdated, NULL);
}

static VOID NTAPI OnFwItemAdded(
    _In_ PVOID Parameter
    )
{
    PFW_EVENT_ITEM fwItem = (PFW_EVENT_ITEM)Parameter;
    PFW_EVENT_NODE fwNode;

    if (!FwNeedsRedraw)
    {
        TreeNew_SetRedraw(FwTreeNewHandle, FALSE);
        FwNeedsRedraw = TRUE;
    }

    fwNode = AddFwNode(fwItem);
    PhDereferenceObject(fwItem);
}

static VOID NTAPI OnFwItemModified(
    _In_ PVOID Parameter
    )
{
    PFW_EVENT_NODE fwNode = (PFW_EVENT_NODE)Parameter;

    UpdateFwNode(fwNode);
}

static VOID NTAPI OnFwItemRemoved(
    _In_ PVOID Parameter
    )
{
    PFW_EVENT_NODE fwNode = (PFW_EVENT_NODE)Parameter;

    if (!FwNeedsRedraw)
    {
        TreeNew_SetRedraw(FwTreeNewHandle, FALSE);
        FwNeedsRedraw = TRUE;
    }

    RemoveFwNode(fwNode);
}

static VOID NTAPI OnFwItemsUpdated(
    _In_ PVOID Parameter
    )
{
    if (FwNeedsRedraw)
    {
        TreeNew_SetRedraw(FwTreeNewHandle, TRUE);
        FwNeedsRedraw = FALSE;
    }

    // Text invalidation
    for (ULONG i = 0; i < FwNodeList->Count; i++)
    {
        PFW_EVENT_NODE node = (PFW_EVENT_NODE)FwNodeList->Items[i];

        // The name and file name never change, so we don't invalidate that.
        memset(&node->TextCache[1], 0, sizeof(PH_STRINGREF) * (FWTNC_MAXIMUM - 2));
        // Always get the newest tooltip text from the process tree.
        PhSwapReference(&node->TooltipText, NULL);
    }

    InvalidateRect(FwTreeNewHandle, NULL, FALSE);
}

VOID NTAPI FwSearchChangedHandler(
    _In_opt_ PVOID Parameter,
    _In_opt_ PVOID Context
    )
{
    if (!FwEnabled)
        return;

    PhApplyTreeNewFilters(&FilterSupport);
}

BOOLEAN NTAPI FwSearchFilterCallback(
    _In_ PPH_TREENEW_NODE Node,
    _In_opt_ PVOID Context
    )
{
    PFW_EVENT_NODE fwNode = (PFW_EVENT_NODE)Node;
    PTOOLSTATUS_WORD_MATCH wordMatch = ToolStatusInterface->WordMatch;

    if (PhIsNullOrEmptyString(ToolStatusInterface->GetSearchboxText()))
        return TRUE;

    if (wordMatch(&fwNode->EventItem->ProcessNameString->sr))
        return TRUE;

    if (wordMatch(&fwNode->EventItem->LocalAddressString->sr))
        return TRUE;

    if (wordMatch(&fwNode->EventItem->RemoteAddressString->sr))
        return TRUE;

    return FALSE;
}

VOID NTAPI FwToolStatusActivateContent(
    _In_ BOOLEAN Select
    )
{
    SetFocus(FwTreeNewHandle);

    if (Select)
    {
        if (TreeNew_GetFlatNodeCount(FwTreeNewHandle) > 0)
            SelectAndEnsureVisibleFwNode((PFW_EVENT_NODE)TreeNew_GetFlatNode(FwTreeNewHandle, 0));
    }
}

HWND NTAPI FwToolStatusGetTreeNewHandle(
    VOID
    )
{
    return FwTreeNewHandle;
}

INT_PTR CALLBACK FwTabErrorDialogProc(
    _In_ HWND hwndDlg,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
    )
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            if (!PhGetOwnTokenAttributes().Elevated)
            {
                SendMessage(GetDlgItem(hwndDlg, IDC_RESTART), BCM_SETSHIELD, 0, TRUE);
            }
            else
            {
                //SetDlgItemText(hwndDlg, IDC_ERROR, L"Unable to start the kernel event tracing session.");
                ShowWindow(GetDlgItem(hwndDlg, IDC_RESTART), SW_HIDE);
            }
        }
        break;
    case WM_COMMAND:
        {
            switch (LOWORD(wParam))
            {
            case IDC_RESTART:
                ProcessHacker_PrepareForEarlyShutdown(PhMainWndHandle);

                if (PhShellProcessHacker(
                    PhMainWndHandle,
                    L"-v -selecttab Firewall",
                    SW_SHOW,
                    PH_SHELL_EXECUTE_ADMIN,
                    PH_SHELL_APP_PROPAGATE_PARAMETERS | PH_SHELL_APP_PROPAGATE_PARAMETERS_IGNORE_VISIBILITY,
                    0,
                    NULL
                    ))
                {
                    ProcessHacker_Destroy(PhMainWndHandle);
                }
                else
                {
                    ProcessHacker_CancelEarlyShutdown(PhMainWndHandle);
                }

                break;
            }
        }
        break;
    case WM_CTLCOLORBTN:
    case WM_CTLCOLORSTATIC:
        {
            SetBkMode((HDC)wParam, TRANSPARENT);
            return (INT_PTR)GetSysColorBrush(COLOR_WINDOW);
        }
        break;
    }

    return FALSE;
}
