/*
 * PtokaX - hub server for Direct Connect peer to peer network.
 
 * Copyright (C) 2004-2017  Petr Kozelka, PPK at PtokaX dot org
 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.
 
 * 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, see <http://www.gnu.org/licenses/>.
 */
 
//---------------------------------------------------------------------------
#include "../core/stdinc.h"
//---------------------------------------------------------------------------
#include "MainWindow.h"
//---------------------------------------------------------------------------
#include "../core/hashBanManager.h"
#include "../core/hashRegManager.h"
#include "../core/LanguageManager.h"
#include "../core/LuaScriptManager.h"
#include "../core/ProfileManager.h"
#include "../core/ServerManager.h"
#include "../core/serviceLoop.h"
#include "../core/SettingManager.h"
#include "../core/utility.h"
//---------------------------------------------------------------------------
#include "GuiSettingManager.h"
#include "GuiUtil.h"
//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#include "AboutDialog.h"
#include "BansDialog.h"
#include "MainWindowPageScripts.h"
#include "MainWindowPageStats.h"
#include "MainWindowPageUsersChat.h"
#include "ProfilesDialog.h"
#include "RangeBansDialog.h"
#include "RegisteredUsersDialog.h"
#include "Resources.h"
#include "SettingDialog.h"
#include "UpdateDialog.h"
#include "../core/TextFileManager.h"
#include "../core/UpdateCheckThread.h"
//---------------------------------------------------------------------------
#define WM_TRAYICON (WM_USER+10)
//---------------------------------------------------------------------------
MainWindow * MainWindow::m_Ptr = nullptr;
//---------------------------------------------------------------------------
static const char sPtokaXDash[] = "PtokaX - ";
static const size_t szPtokaXDashLen = sizeof(sPtokaXDash)-1;
//---------------------------------------------------------------------------
#ifndef _WIN64
	static uint64_t (*GetActualTick)();
	
	uint64_t PXGetTickCount() {
		return (uint64_t)(::GetTickCount() / 1000);
	}
	
	typedef ULONGLONG (WINAPI *GTC64)(void);
	GTC64 pGTC64 = nullptr;
	
	uint64_t PXGetTickCount64() {
		return (pGTC64() / 1000);
	}
#endif
//---------------------------------------------------------------------------
 
MainWindow::MainWindow() : m_hWnd(nullptr), m_ui64LastTrayMouseMove(0), m_uiTaskBarCreated(0) {
    memset(&m_hWndWindowItems, 0, sizeof(m_hWndWindowItems));
    memset(&m_MainWindowPages, 0, sizeof(m_MainWindowPages));
}
//---------------------------------------------------------------------------
 
MainWindow::~MainWindow() {
    delete GuiSettingManager::m_Ptr;
 
    for(uint8_t ui8i = 0; ui8i < (sizeof(m_MainWindowPages) / sizeof(m_MainWindowPages[0])); ui8i++) {
        delete m_MainWindowPages[ui8i];
    }
 
    NOTIFYICONDATA nid;
    memset(&nid, 0, sizeof(NOTIFYICONDATA));
    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = m_hWnd;
    nid.uID = 0;
    ::Shell_NotifyIcon(NIM_DELETE, &nid);
    ::DeleteObject(GuiSettingManager::m_hFont);
}
//---------------------------------------------------------------------------
 
LRESULT CALLBACK MainWindow::StaticMainWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	if(uMsg == WM_NCCREATE) {
        ::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)MainWindow::m_Ptr);
		MainWindow::m_Ptr->m_hWnd = hWnd;
	} else {
        if(::GetWindowLongPtr(hWnd, GWLP_USERDATA) == 0) {
            return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }
 
	return MainWindow::m_Ptr->MainWindowProc(uMsg, wParam, lParam);
}
//------------------------------------------------------------------------------
 
LRESULT MainWindow::MainWindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch(uMsg) {
        case WM_CREATE: {
            {
                HWND hCombo = ::CreateWindowEx(0, WC_COMBOBOX, nullptr, WS_CHILD | WS_VSCROLL | CBS_DROPDOWNLIST, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hWnd, nullptr,
                    ServerManager::m_hInstance, nullptr);
 
                if(hCombo != nullptr) {
                    ::SendMessage(hCombo, WM_SETFONT, (WPARAM)GuiSettingManager::m_hFont, MAKELPARAM(TRUE, 0));
 
					GuiSettingManager::m_iGroupBoxMargin = (int)::SendMessage(hCombo, CB_GETITEMHEIGHT, (WPARAM)-1, 0);
					GuiSettingManager::m_iEditHeight = GuiSettingManager::m_iGroupBoxMargin + (::GetSystemMetrics(SM_CXFIXEDFRAME) * 2);
					GuiSettingManager::m_iTextHeight = GuiSettingManager::m_iGroupBoxMargin - 2;
					GuiSettingManager::m_iCheckHeight = max(GuiSettingManager::m_iTextHeight - 2, ::GetSystemMetrics(SM_CYSMICON));
 
                    ::DestroyWindow(hCombo);
                }
 
                HWND hUpDown = ::CreateWindowEx(0, UPDOWN_CLASS, nullptr, WS_CHILD | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_SETBUDDYINT,
                    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, GuiSettingManager::m_iEditHeight, m_hWnd, nullptr, ServerManager::m_hInstance, nullptr);
 
                if(hUpDown != nullptr) {
                    RECT rcUpDown;
                    ::GetWindowRect(hUpDown, &rcUpDown);
 
					GuiSettingManager::m_iUpDownWidth = rcUpDown.right - rcUpDown.left;
 
                    ::DestroyWindow(hUpDown);
                }
 
				GuiSettingManager::m_iOneLineGB = GuiSettingManager::m_iGroupBoxMargin + GuiSettingManager::m_iEditHeight + 8;
				GuiSettingManager::m_iOneLineOneChecksGB = GuiSettingManager::m_iGroupBoxMargin + GuiSettingManager::m_iCheckHeight + GuiSettingManager::m_iEditHeight + 12;
				GuiSettingManager::m_iOneLineTwoChecksGB = GuiSettingManager::m_iGroupBoxMargin + (2 * GuiSettingManager::m_iCheckHeight) + GuiSettingManager::m_iEditHeight + 15;
 
                SettingPage::m_iOneCheckGB = GuiSettingManager::m_iGroupBoxMargin + GuiSettingManager::m_iCheckHeight + 8;
                SettingPage::m_iTwoChecksGB = GuiSettingManager::m_iGroupBoxMargin + (2 * GuiSettingManager::m_iCheckHeight) + 3 + 8;
                SettingPage::m_iOneLineTwoGroupGB = GuiSettingManager::m_iGroupBoxMargin + GuiSettingManager::m_iEditHeight + (2 * GuiSettingManager::m_iOneLineGB) + 7;
                SettingPage::m_iTwoLineGB = GuiSettingManager::m_iGroupBoxMargin  + (2 * GuiSettingManager::m_iEditHeight) + 13;
                SettingPage::m_iThreeLineGB = GuiSettingManager::m_iGroupBoxMargin + (3 * GuiSettingManager::m_iEditHeight) + 18;
            }
 
            RECT rcMain;
            ::GetClientRect(m_hWnd, &rcMain);
 
            DWORD dwTabsStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | WS_TABSTOP | TCS_TABS | TCS_FOCUSNEVER;
 
            BOOL bHotTrackEnabled = FALSE;
            ::SystemParametersInfo(SPI_GETHOTTRACKING, 0, &bHotTrackEnabled, 0);
 
            if(bHotTrackEnabled == TRUE) {
                dwTabsStyle |= TCS_HOTTRACK;
            }
 
            m_hWndWindowItems[TC_TABS] = ::CreateWindowEx(0, WC_TABCONTROL, nullptr, dwTabsStyle, 0, 0, rcMain.right, GuiSettingManager::m_iEditHeight, m_hWnd, nullptr, ServerManager::m_hInstance, nullptr);
            ::SendMessage(m_hWndWindowItems[TC_TABS], WM_SETFONT, (WPARAM)GuiSettingManager::m_hFont, MAKELPARAM(TRUE, 0));
 
            TCITEM tcItem = { 0 };
            tcItem.mask = TCIF_TEXT | TCIF_PARAM;
 
            for(uint8_t ui8i = 0; ui8i < (sizeof(m_MainWindowPages) / sizeof(m_MainWindowPages[0])); ui8i++) {
                if(m_MainWindowPages[ui8i] != nullptr) {
                    if(m_MainWindowPages[ui8i]->CreateMainWindowPage(m_hWnd) == false) {
                        ::MessageBox(m_hWnd, "Main window creation failed!", g_sPtokaXTitle, MB_OK);
                    }
 
                    tcItem.pszText = m_MainWindowPages[ui8i]->GetPageName();
                    tcItem.lParam = (LPARAM)m_MainWindowPages[ui8i];
                    ::SendMessage(m_hWndWindowItems[TC_TABS], TCM_INSERTITEM, ui8i, (LPARAM)&tcItem);
                }
 
                if(ui8i == 0 && GuiSettingManager::m_Ptr->m_i32Integers[GUISETINT_MAIN_WINDOW_HEIGHT] == GuiSettingManager::m_Ptr->GetDefaultInteger(GUISETINT_MAIN_WINDOW_HEIGHT)) {
                    RECT rcPage = { 0 };
                    ::GetWindowRect(m_MainWindowPages[0]->m_hWnd, &rcPage);
 
                    int iDiff = (rcMain.bottom) - (rcPage.bottom-rcPage.top) - (GuiSettingManager::m_iEditHeight + 1);
 
                    ::GetWindowRect(m_hWnd, &rcMain);
 
                    if(iDiff != 0) {
                        ::SetWindowPos(m_hWnd, nullptr, 0, 0, (rcMain.right-rcMain.left), (rcMain.bottom-rcMain.top) - iDiff, SWP_NOMOVE | SWP_NOZORDER);
                    }
                }
            }
 
            GuiSettingManager::m_wpOldTabsProc = (WNDPROC)::SetWindowLongPtr(m_hWndWindowItems[TC_TABS], GWLP_WNDPROC, (LONG_PTR)TabsProc);
 
            if(SettingManager::m_Ptr->m_bBools[SETBOOL_CHECK_NEW_RELEASES] == true) {
                // Create update check thread
                UpdateCheckThread::m_Ptr = new (std::nothrow) UpdateCheckThread();
                if(UpdateCheckThread::m_Ptr == nullptr) {
                    AppendDebugLog("%s - [MEM] Cannot allocate UpdateCheckThread::m_Ptr in MainWindow::MainWindowProc::WM_CREATE\n");
                    exit(EXIT_FAILURE);
                }
 
                // Start the update check thread
                UpdateCheckThread::m_Ptr->Resume();
            }
 
            return 0;
        }
        case WM_CLOSE:
            if(::MessageBox(m_hWnd, (string(LanguageManager::m_Ptr->m_sTexts[LAN_ARE_YOU_SURE], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_ARE_YOU_SURE])+" ?").c_str(), g_sPtokaXTitle, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDYES) {
                ServerManager::m_bIsClose = true;
 
                RECT rcMain;
                ::GetWindowRect(m_hWnd, &rcMain);
 
                GuiSettingManager::m_Ptr->SetInteger(GUISETINT_MAIN_WINDOW_WIDTH, rcMain.right - rcMain.left);
                GuiSettingManager::m_Ptr->SetInteger(GUISETINT_MAIN_WINDOW_HEIGHT, rcMain.bottom - rcMain.top);
 
                // stop server if running and save settings
                if(ServerManager::m_bServerRunning == true) {
                    ServerManager::Stop();
                    return 0;
                } else {
                    ServerManager::FinalClose();
                }
 
                break;
            }
 
            return 0;
        case WM_DESTROY:
            ::PostQuitMessage(1);
            break;
        case WM_TRAYICON:
            if(lParam == WM_MOUSEMOVE) {
#ifndef _WIN64
                if(m_ui64LastTrayMouseMove != GetActualTick()) {
					m_ui64LastTrayMouseMove = GetActualTick();
#else
                if(m_ui64LastTrayMouseMove != GetTickCount64()) {
					m_ui64LastTrayMouseMove = GetTickCount64();
#endif
                    NOTIFYICONDATA nid;
                    memset(&nid, 0, sizeof(NOTIFYICONDATA));
                    nid.cbSize = sizeof(NOTIFYICONDATA);
                    nid.hWnd = m_hWnd;
                    nid.uID = 0;
                    nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
                    nid.hIcon = (HICON)::LoadImage(ServerManager::m_hInstance, MAKEINTRESOURCE(IDR_MAINICONSMALL), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR | LR_SHARED);
                    nid.uCallbackMessage = WM_TRAYICON;
 
					char msg[256];
					int iMsgLen = 0;
                    if(ServerManager::m_bServerRunning == false) {
						msg[0] = '\0';
                    } else {
                        iMsgLen = snprintf(msg, 256, " (%s: %u)", LanguageManager::m_Ptr->m_sTexts[LAN_USERS], ServerManager::m_ui32Logged);
                        if(iMsgLen <= 0) {
                            return 0;
                        }
					}
 
					const size_t szSize = sizeof(nid.szTip);
 
					if(szSize < (size_t)(SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME]+iMsgLen+10)) {
						nid.szTip[szSize-1] = '\0';
						memcpy(nid.szTip+(szSize-(iMsgLen+1)), msg, iMsgLen);
						nid.szTip[szSize-(iMsgLen+2)] = '.';
						nid.szTip[szSize-(iMsgLen+3)] = '.';
						nid.szTip[szSize-(iMsgLen+4)] = '.';
						memcpy(nid.szTip+szPtokaXDashLen, SettingManager::m_Ptr->m_sTexts[SETTXT_HUB_NAME], szSize-(iMsgLen+13));
						memcpy(nid.szTip, sPtokaXDash, szPtokaXDashLen);
					} else {
						memcpy(nid.szTip, sPtokaXDash, szPtokaXDashLen);
						memcpy(nid.szTip+szPtokaXDashLen, SettingManager::m_Ptr->m_sTexts[SETTXT_HUB_NAME], SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME]);
						memcpy(nid.szTip+szPtokaXDashLen+SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME], msg, iMsgLen);
						nid.szTip[szPtokaXDashLen+SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME]+iMsgLen] = '\0';
                    }
                    ::Shell_NotifyIcon(NIM_MODIFY, &nid);
				}
            } else if(lParam == WM_LBUTTONUP) {
                ::ShowWindow(m_hWnd, SW_SHOW);
                ::ShowWindow(m_hWnd, SW_RESTORE);
            }
 
            return 0;
        case WM_SIZE:
            if(wParam == SIZE_MINIMIZED && SettingManager::m_Ptr->m_bBools[SETBOOL_ENABLE_TRAY_ICON] == true) {
                ::ShowWindow(m_hWnd, SW_HIDE);
            } else {
                RECT rc;
                ::SetRect(&rc, 0, 0, GET_X_LPARAM(lParam), GuiSettingManager::m_iEditHeight);
                ::SendMessage(m_hWndWindowItems[TC_TABS], TCM_ADJUSTRECT, FALSE, (LPARAM)&rc);
 
                HDWP hdwp = ::BeginDeferWindowPos(3);
 
                ::DeferWindowPos(hdwp, m_hWndWindowItems[TC_TABS], nullptr, 0, 0, GET_X_LPARAM(lParam), GuiSettingManager::m_iEditHeight, SWP_NOMOVE | SWP_NOZORDER);
 
                for(uint8_t ui8i = 0; ui8i < (sizeof(m_MainWindowPages) / sizeof(m_MainWindowPages[0])); ui8i++) {
                    if(m_MainWindowPages[ui8i] != nullptr) {
                        ::DeferWindowPos(hdwp, m_MainWindowPages[ui8i]->m_hWnd, nullptr, 0, GuiSettingManager::m_iEditHeight + 1, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) - (GuiSettingManager::m_iEditHeight + 1),
                            SWP_NOMOVE | SWP_NOZORDER);
                    }
                }
 
                ::EndDeferWindowPos(hdwp);
            }
 
            return 0;
        case WM_GETMINMAXINFO: {
            MINMAXINFO *mminfo = (MINMAXINFO*)lParam;
            mminfo->ptMinTrackSize.x = ScaleGui(GuiSettingManager::m_Ptr->GetDefaultInteger(GUISETINT_MAIN_WINDOW_WIDTH));
            mminfo->ptMinTrackSize.y = ScaleGui(GuiSettingManager::m_Ptr->GetDefaultInteger(GUISETINT_MAIN_WINDOW_HEIGHT));
 
            return 0;
        }
        case WM_NOTIFY:
            if(((LPNMHDR)lParam)->hwndFrom == m_hWndWindowItems[TC_TABS]) {
                if(((LPNMHDR)lParam)->code == TCN_SELCHANGE) {
                    OnSelChanged();
                    return 0;
                } else if(((LPNMHDR)lParam)->code == TCN_KEYDOWN ) {
                    NMTCKEYDOWN * ptckd = (NMTCKEYDOWN *)lParam;
                    if(ptckd->wVKey == VK_TAB) {
                        int iPage = (int)::SendMessage(m_hWndWindowItems[TC_TABS], TCM_GETCURSEL, 0, 0);
 
                        if(iPage == -1) {
                            break;
                        }
 
                        TCITEM tcItem = { 0 };
                        tcItem.mask = TCIF_PARAM;
 
                        if((BOOL)::SendMessage(m_hWndWindowItems[TC_TABS], TCM_GETITEM, iPage, (LPARAM)&tcItem) == FALSE) {
                            break;
                        }
 
                        if(tcItem.lParam == 0) {
                            ::MessageBox(m_hWnd, "Not implemented!", g_sPtokaXTitle, MB_OK);
                        }
 
                        MainWindowPage * curMainWindowPage = reinterpret_cast<MainWindowPage *>(tcItem.lParam);
 
                        if((::GetKeyState(VK_SHIFT) & 0x8000) > 0) {
                            curMainWindowPage->FocusLastItem();
                            return 0;
                        } else {
                            curMainWindowPage->FocusFirstItem();
                            return 0;
                        }
                    }
                }
            }
 
            break;
        case WM_COMMAND:
           switch(LOWORD(wParam)) {
                case IDC_EXIT:
                    ::PostMessage(m_hWnd, WM_CLOSE, 0, 0);
                    return 0;
                case IDC_SETTINGS: {
                    SettingDialog::m_Ptr = new (std::nothrow) SettingDialog();
 
                    if(SettingDialog::m_Ptr != nullptr) {
                        SettingDialog::m_Ptr->DoModal(m_hWnd);
                    }
 
                    return 0;
                }
                case IDC_REG_USERS: {
                    RegisteredUsersDialog::m_Ptr = new (std::nothrow) RegisteredUsersDialog();
 
                    if(RegisteredUsersDialog::m_Ptr != nullptr) {
                        RegisteredUsersDialog::m_Ptr->DoModal(m_hWnd);
                    }
 
                    return 0;
                }
                case IDC_PROFILES: {
                    ProfilesDialog::m_Ptr = new (std::nothrow) ProfilesDialog();
 
                    if(ProfilesDialog::m_Ptr != nullptr) {
                        ProfilesDialog::m_Ptr->DoModal(m_hWnd);
                    }
 
                    return 0;
                }
                case IDC_BANS: {
                    BansDialog::m_Ptr = new (std::nothrow) BansDialog();
 
                    if(BansDialog::m_Ptr != nullptr) {
                        BansDialog::m_Ptr->DoModal(m_hWnd);
                    }
 
                    return 0;
				}
                case IDC_RANGE_BANS: {
                    RangeBansDialog::m_Ptr = new (std::nothrow) RangeBansDialog();
 
                    if(RangeBansDialog::m_Ptr != nullptr) {
                        RangeBansDialog::m_Ptr->DoModal(m_hWnd);
                    }
 
                    return 0;
                }
                case IDC_ABOUT: {
                    AboutDialog * pAboutDlg = new (std::nothrow) AboutDialog();
 
                    if(pAboutDlg != nullptr) {
                        pAboutDlg->DoModal(m_hWnd);
                    }
 
                    return 0;
                }
                case IDC_HOMEPAGE:
                    ::ShellExecute(nullptr, nullptr, "http://www.PtokaX.org", nullptr, nullptr, SW_SHOWNORMAL);
                    return 0;
                case IDC_FORUM:
                    ::ShellExecute(nullptr, nullptr, "http://forum.PtokaX.org/", nullptr, nullptr, SW_SHOWNORMAL);
                    return 0;
                case IDC_WIKI:
                    ::ShellExecute(nullptr, nullptr, "http://wiki.PtokaX.org", nullptr, nullptr, SW_SHOWNORMAL);
                    return 0;
                case IDC_UPDATE_CHECK: {
                    UpdateDialog::m_Ptr = new (std::nothrow) UpdateDialog();
 
                    if(UpdateDialog::m_Ptr != nullptr) {
                        UpdateDialog::m_Ptr->DoModal(m_hWnd);
 
                        // First destroy old update check thread if any
                        if(UpdateCheckThread::m_Ptr != nullptr) {
                            UpdateCheckThread::m_Ptr->Close();
                            UpdateCheckThread::m_Ptr->WaitFor();
                        }
 
                        delete UpdateCheckThread::m_Ptr;
                        UpdateCheckThread::m_Ptr = nullptr;
 
                        // Create update check thread
                        UpdateCheckThread::m_Ptr = new (std::nothrow) UpdateCheckThread();
                        if(UpdateCheckThread::m_Ptr == nullptr) {
                            AppendDebugLog("%s - [MEM] Cannot allocate UpdateCheckThread::m_Ptr in MainWindow::MainWindowProc::IDC_UPDATE_CHECK\n");
                            exit(EXIT_FAILURE);
                        }
 
                        // Start the update check thread
                        UpdateCheckThread::m_Ptr->Resume();
                    }
 
                    return 0;
                }
                case IDC_SAVE_SETTINGS:
                    BanManager::m_Ptr->Save(true);
                    ProfileManager::m_Ptr->SaveProfiles();
                    RegManager::m_Ptr->Save();
                    ScriptManager::m_Ptr->SaveScripts();
                    SettingManager::m_Ptr->Save();
 
                    ::MessageBox(m_hWnd, LanguageManager::m_Ptr->m_sTexts[LAN_SETTINGS_SAVED], g_sPtokaXTitle, MB_OK | MB_ICONINFORMATION);
                    return 0;
                case IDC_RELOAD_TXTS:
                    TextFilesManager::m_Ptr->RefreshTextFiles();
 
                    ::MessageBox(m_hWnd, (string(LanguageManager::m_Ptr->m_sTexts[LAN_TXT_FILES_RELOADED], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_TXT_FILES_RELOADED])+".").c_str(), g_sPtokaXTitle, MB_OK | MB_ICONINFORMATION);
 
                    return 0;
            }
 
            break;
        case WM_QUERYENDSESSION:
            BanManager::m_Ptr->Save(true);
            ProfileManager::m_Ptr->SaveProfiles();
            RegManager::m_Ptr->Save();
            ScriptManager::m_Ptr->SaveScripts();
            SettingManager::m_Ptr->Save();
 
            AppendLog("Serving stopped (Windows shutdown).");
 
            return TRUE;
        case WM_UPDATE_CHECK_MSG: {
            char * sMsg = (char *)lParam;
            if(UpdateDialog::m_Ptr != nullptr) {
                UpdateDialog::m_Ptr->Message(sMsg);
            }
 
            free(sMsg);
 
            return 0;
        }
        case WM_UPDATE_CHECK_DATA: {
            char * sMsg = (char *)lParam;
 
            if(UpdateDialog::m_Ptr == nullptr) {
                UpdateDialog::m_Ptr = new (std::nothrow) UpdateDialog();
 
                if(UpdateDialog::m_Ptr != nullptr) {
                    if(UpdateDialog::m_Ptr->ParseData(sMsg, m_hWnd) == false) {
                        delete UpdateDialog::m_Ptr;
                    }
                }
            } else {
                UpdateDialog::m_Ptr->ParseData(sMsg, m_hWnd);
            }
 
            free(sMsg);
 
            return 0;
        }
        case WM_UPDATE_CHECK_TERMINATE:
            if(UpdateCheckThread::m_Ptr != nullptr) {
                UpdateCheckThread::m_Ptr->Close();
                UpdateCheckThread::m_Ptr->WaitFor();
            }
 
            delete UpdateCheckThread::m_Ptr;
            UpdateCheckThread::m_Ptr = nullptr;
 
            return 0;
        case WM_SETFOCUS:
            ::SetFocus(m_hWndWindowItems[TC_TABS]);
            return 0;
    }
 
	if(uMsg == m_uiTaskBarCreated) {
		UpdateSysTray();
		return 0;
	}
 
	return ::DefWindowProc(m_hWnd, uMsg, wParam, lParam);
}
//------------------------------------------------------------------------------
 
HWND MainWindow::CreateEx() {
    GuiSettingManager::m_Ptr = new (std::nothrow) GuiSettingManager();
 
    if(GuiSettingManager::m_Ptr == nullptr) {
        AppendDebugLog("%s - [MEM] Cannot allocate GuiSettingManager::m_Ptr in MainWindow::MainWindow\n");
        exit(EXIT_FAILURE);
    }
 
	INITCOMMONCONTROLSEX iccx = { sizeof(INITCOMMONCONTROLSEX), ICC_BAR_CLASSES | ICC_COOL_CLASSES | ICC_DATE_CLASSES | ICC_LINK_CLASS | ICC_LISTVIEW_CLASSES |
        ICC_STANDARD_CLASSES | ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_UPDOWN_CLASS };
	InitCommonControlsEx(&iccx);
 
	m_MainWindowPages[0] = new (std::nothrow) MainWindowPageStats();
	m_MainWindowPages[1] = new (std::nothrow) MainWindowPageUsersChat();
	m_MainWindowPages[2] = new (std::nothrow) MainWindowPageScripts();
 
    for(uint8_t ui8i = 0; ui8i < 3; ui8i++) {
        if(m_MainWindowPages[ui8i] == nullptr) {
            AppendDebugLogFormat("[MEM] Cannot allocate MainWindowPage[%" PRIu8 "] in MainWindow::MainWindow\n", ui8i);
            exit(EXIT_FAILURE);
        }
    }
 
	m_uiTaskBarCreated = ::RegisterWindowMessage("TaskbarCreated");
 
	NONCLIENTMETRICS NCM = { 0 };
	NCM.cbSize = sizeof(NONCLIENTMETRICS);
 
    OSVERSIONINFOEX ver;
	memset(&ver, 0, sizeof(OSVERSIONINFOEX));
	ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
 
#ifndef _WIN64
    if(::GetVersionEx((OSVERSIONINFO*)&ver) != 0 && ver.dwPlatformId == VER_PLATFORM_WIN32_NT && ver.dwMajorVersion < 6) {
        NCM.cbSize -= sizeof(int);
    }
#endif
 
	::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &NCM, 0);
 
    if(NCM.lfMessageFont.lfHeight > 0) {
        GuiSettingManager::m_fScaleFactor = (float)(NCM.lfMessageFont.lfHeight / 12.0);
    } else if(NCM.lfMessageFont.lfHeight < 0) {
        GuiSettingManager::m_fScaleFactor = float(NCM.lfMessageFont.lfHeight / -12.0);
    }
 
    GuiSettingManager::m_hFont = ::CreateFontIndirect(&NCM.lfMessageFont);
 
    GuiSettingManager::m_hArrowCursor = (HCURSOR)::LoadImage(nullptr, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
    GuiSettingManager::m_hVerticalCursor = (HCURSOR)::LoadImage(nullptr, IDC_SIZEWE, IMAGE_CURSOR, 0, 0, LR_SHARED | LR_DEFAULTSIZE);
 
    HMENU hMainMenu = ::CreateMenu();
 
    HMENU hFileMenu = ::CreatePopupMenu();
    ::AppendMenu(hFileMenu, MF_STRING, IDC_RELOAD_TXTS, (string(LanguageManager::m_Ptr->m_sTexts[LAN_RELOAD_TEXT_FILES], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_RELOAD_TEXT_FILES]) + "...").c_str());
    ::AppendMenu(hFileMenu, MF_SEPARATOR, 0, 0);
    ::AppendMenu(hFileMenu, MF_STRING, IDC_SETTINGS, (string(LanguageManager::m_Ptr->m_sTexts[LAN_MENU_SETTINGS], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_MENU_SETTINGS]) + "...").c_str());
    ::AppendMenu(hFileMenu, MF_STRING, IDC_SAVE_SETTINGS, (string(LanguageManager::m_Ptr->m_sTexts[LAN_SAVE_SETTINGS], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_SAVE_SETTINGS]) + "...").c_str());
    ::AppendMenu(hFileMenu, MF_SEPARATOR, 0, 0);
    ::AppendMenu(hFileMenu, MF_STRING, IDC_EXIT, LanguageManager::m_Ptr->m_sTexts[LAN_EXIT]);
 
    ::AppendMenu(hMainMenu, MF_POPUP, (UINT_PTR)hFileMenu, LanguageManager::m_Ptr->m_sTexts[LAN_FILE]);
 
    HMENU hViewMenu = ::CreatePopupMenu();
    ::AppendMenu(hViewMenu, MF_STRING, IDC_REG_USERS, LanguageManager::m_Ptr->m_sTexts[LAN_REG_USERS]);
    ::AppendMenu(hViewMenu, MF_SEPARATOR, 0, 0);
    ::AppendMenu(hViewMenu, MF_STRING, IDC_PROFILES, LanguageManager::m_Ptr->m_sTexts[LAN_PROFILES]);
    ::AppendMenu(hViewMenu, MF_SEPARATOR, 0, 0);
    ::AppendMenu(hViewMenu, MF_STRING, IDC_BANS, LanguageManager::m_Ptr->m_sTexts[LAN_BANS]);
    ::AppendMenu(hViewMenu, MF_STRING, IDC_RANGE_BANS, LanguageManager::m_Ptr->m_sTexts[LAN_RANGE_BANS]);
 
    ::AppendMenu(hMainMenu, MF_POPUP, (UINT_PTR)hViewMenu, LanguageManager::m_Ptr->m_sTexts[LAN_VIEW]);
 
    HMENU hHelpMenu = ::CreatePopupMenu();
    ::AppendMenu(hHelpMenu, MF_STRING, IDC_UPDATE_CHECK, (string(LanguageManager::m_Ptr->m_sTexts[LAN_CHECK_FOR_UPDATE], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_CHECK_FOR_UPDATE]) + "...").c_str());
    ::AppendMenu(hHelpMenu, MF_SEPARATOR, 0, 0);
    ::AppendMenu(hHelpMenu, MF_STRING, IDC_HOMEPAGE, (string("PtokaX ") +LanguageManager::m_Ptr->m_sTexts[LAN_WEBSITE]).c_str());
    ::AppendMenu(hHelpMenu, MF_STRING, IDC_FORUM, (string("PtokaX ") +LanguageManager::m_Ptr->m_sTexts[LAN_FORUM]).c_str());
    ::AppendMenu(hHelpMenu, MF_STRING, IDC_WIKI, (string("PtokaX ") +LanguageManager::m_Ptr->m_sTexts[LAN_WIKI]).c_str());
    ::AppendMenu(hHelpMenu, MF_SEPARATOR, 0, 0);
    ::AppendMenu(hHelpMenu, MF_STRING, IDC_ABOUT, (string(LanguageManager::m_Ptr->m_sTexts[LAN_MENU_ABOUT], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_MENU_ABOUT]) + " PtokaX").c_str());
 
    ::AppendMenu(hMainMenu, MF_POPUP, (UINT_PTR)hHelpMenu, LanguageManager::m_Ptr->m_sTexts[LAN_HELP]);
 
    WNDCLASSEX m_wc;
    memset(&m_wc, 0, sizeof(WNDCLASSEX));
    m_wc.cbSize = sizeof(WNDCLASSEX);
    m_wc.lpfnWndProc = StaticMainWindowProc;
    m_wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
    m_wc.lpszClassName = g_sPtokaXTitle;
    m_wc.hInstance = ServerManager::m_hInstance;
	m_wc.hCursor = ::LoadCursor(m_wc.hInstance, IDC_ARROW);
	m_wc.style = CS_HREDRAW | CS_VREDRAW;
	m_wc.hIcon = (HICON)::LoadImage(m_wc.hInstance, MAKEINTRESOURCE(IDR_MAINICON), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR | LR_SHARED);
	m_wc.hIconSm = (HICON)::LoadImage(m_wc.hInstance, MAKEINTRESOURCE(IDR_MAINICONSMALL), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR | LR_SHARED);
 
	ATOM atom = ::RegisterClassEx(&m_wc);
 
    m_hWnd = ::CreateWindowEx(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, MAKEINTATOM(atom), (string(SettingManager::m_Ptr->m_sTexts[SETTXT_HUB_NAME], (size_t)SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME]) + " | " + g_sPtokaXTitle).c_str(),
        WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, CW_USEDEFAULT, CW_USEDEFAULT, ScaleGuiDefaultsOnly(GUISETINT_MAIN_WINDOW_WIDTH), ScaleGuiDefaultsOnly(GUISETINT_MAIN_WINDOW_HEIGHT), nullptr, hMainMenu, ServerManager::m_hInstance, nullptr);
 
#ifndef _WIN64
	if(::GetVersionEx((OSVERSIONINFO*)&ver) != 0 && ver.dwPlatformId == VER_PLATFORM_WIN32_NT && ver.dwMajorVersion >= 6) {
        pGTC64 = (GTC64)::GetProcAddress(::GetModuleHandle("kernel32.dll"), "GetTickCount64");
        if(pGTC64 != nullptr) {
            GetActualTick = PXGetTickCount64;
            return m_hWnd;
        }
    }
 
    GetActualTick = PXGetTickCount;
#endif
 
	return m_hWnd;
}
//---------------------------------------------------------------------------
 
void MainWindow::UpdateSysTray() const {
    if(ServerManager::m_bCmdNoTray == false) {
        NOTIFYICONDATA nid;
        memset(&nid, 0, sizeof(NOTIFYICONDATA));
        nid.cbSize = sizeof(NOTIFYICONDATA);
        nid.hWnd = m_hWnd;
        nid.uID = 0;
        nid.uFlags = NIF_ICON | NIF_MESSAGE;
        nid.hIcon = (HICON)::LoadImage(ServerManager::m_hInstance, MAKEINTRESOURCE(IDR_MAINICONSMALL), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR | LR_SHARED);
        nid.uCallbackMessage = WM_TRAYICON;
 
        if(SettingManager::m_Ptr->m_bBools[SETBOOL_ENABLE_TRAY_ICON] == true) {
            ::Shell_NotifyIcon(NIM_ADD, &nid);
        } else {
            ::Shell_NotifyIcon(NIM_DELETE, &nid);
        }
    }
}
//---------------------------------------------------------------------------
 
void MainWindow::UpdateStats() const {
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_JOINS_VALUE], string(ServerManager::m_ui32Joins).c_str());
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_PARTS_VALUE], string(ServerManager::m_ui32Parts).c_str());
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_ACTIVE_VALUE], string(ServerManager::m_ui32Joins - ServerManager::m_ui32Parts).c_str());
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_ONLINE_VALUE], string(ServerManager::m_ui32Logged).c_str());
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_PEAK_VALUE], string(ServerManager::m_ui32Peak).c_str());
 
    char msg[256];
	int iMsglen = snprintf(msg, 256, "%s (%s)", formatBytes(ServerManager::m_ui64BytesRead), formatBytesPerSecond(ServerManager::m_ui32ActualBytesRead));
    if(iMsglen > 0) {
        ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_RECEIVED_VALUE], msg);
	}
 
	iMsglen = snprintf(msg, 256, "%s (%s)", formatBytes(ServerManager::m_ui64BytesSent), formatBytesPerSecond(ServerManager::m_ui32ActualBytesSent));
    if(iMsglen > 0) {
		::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_SENT_VALUE], msg);
	}
}
//---------------------------------------------------------------------------
 
void MainWindow::UpdateTitleBar() {
    ::SetWindowText(m_hWnd, (string(SettingManager::m_Ptr->m_sTexts[SETTXT_HUB_NAME], (size_t)SettingManager::m_Ptr->m_ui16TextsLens[SETTXT_HUB_NAME]) + " | " + g_sPtokaXTitle).c_str());
}
//---------------------------------------------------------------------------
 
void MainWindow::UpdateLanguage() {
    for(uint8_t ui8i = 0; ui8i < (sizeof(m_MainWindowPages) / sizeof(m_MainWindowPages[0])); ui8i++) {
        if(m_MainWindowPages[ui8i] != nullptr) {
			m_MainWindowPages[ui8i]->UpdateLanguage();
 
            TCITEM tcItem = { 0 };
            tcItem.mask = TCIF_TEXT;
 
            tcItem.pszText = m_MainWindowPages[ui8i]->GetPageName();
            ::SendMessage(m_hWndWindowItems[TC_TABS], TCM_SETITEM, ui8i, (LPARAM)&tcItem);
        }
    }
 
    HMENU hMenu = ::GetMenu(m_hWnd);
 
    ::ModifyMenu(hMenu, 0, MF_BYPOSITION, 0, LanguageManager::m_Ptr->m_sTexts[LAN_FILE]);
    ::ModifyMenu(hMenu, 1, MF_BYPOSITION, 0, LanguageManager::m_Ptr->m_sTexts[LAN_VIEW]);
    ::ModifyMenu(hMenu, 2, MF_BYPOSITION, 0, LanguageManager::m_Ptr->m_sTexts[LAN_HELP]);
 
    ::ModifyMenu(hMenu, IDC_EXIT, MF_BYCOMMAND, IDC_EXIT, LanguageManager::m_Ptr->m_sTexts[LAN_EXIT]);
    ::ModifyMenu(hMenu, IDC_REG_USERS, MF_BYCOMMAND, IDC_REG_USERS, LanguageManager::m_Ptr->m_sTexts[LAN_REG_USERS]);
    ::ModifyMenu(hMenu, IDC_PROFILES, MF_BYCOMMAND, IDC_PROFILES, LanguageManager::m_Ptr->m_sTexts[LAN_PROFILES]);
    ::ModifyMenu(hMenu, IDC_BANS, MF_BYCOMMAND, IDC_BANS, LanguageManager::m_Ptr->m_sTexts[LAN_BANS]);
    ::ModifyMenu(hMenu, IDC_RANGE_BANS, MF_BYCOMMAND, IDC_RANGE_BANS, LanguageManager::m_Ptr->m_sTexts[LAN_RANGE_BANS]);
 
    ::ModifyMenu(hMenu, IDC_HOMEPAGE, MF_BYCOMMAND, IDC_HOMEPAGE, (string("PtokaX ") +LanguageManager::m_Ptr->m_sTexts[LAN_WEBSITE]).c_str());
    ::ModifyMenu(hMenu, IDC_FORUM, MF_BYCOMMAND, IDC_FORUM, (string("PtokaX ") +LanguageManager::m_Ptr->m_sTexts[LAN_FORUM]).c_str());
    ::ModifyMenu(hMenu, IDC_WIKI, MF_BYCOMMAND, IDC_WIKI, (string("PtokaX ") +LanguageManager::m_Ptr->m_sTexts[LAN_WIKI]).c_str());
 
    ::ModifyMenu(hMenu, IDC_ABOUT, MF_BYCOMMAND, IDC_ABOUT,
        (string(LanguageManager::m_Ptr->m_sTexts[LAN_MENU_ABOUT], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_MENU_ABOUT]) + " PtokaX").c_str());
 
    ::ModifyMenu(hMenu, IDC_UPDATE_CHECK, MF_BYCOMMAND, IDC_UPDATE_CHECK,
        (string(LanguageManager::m_Ptr->m_sTexts[LAN_CHECK_FOR_UPDATE], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_CHECK_FOR_UPDATE]) + "...").c_str());
    ::ModifyMenu(hMenu, IDC_RELOAD_TXTS, MF_BYCOMMAND, IDC_RELOAD_TXTS,
        (string(LanguageManager::m_Ptr->m_sTexts[LAN_RELOAD_TEXT_FILES], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_RELOAD_TEXT_FILES]) + "...").c_str());
    ::ModifyMenu(hMenu, IDC_SETTINGS, MF_BYCOMMAND, IDC_SETTINGS,
        (string(LanguageManager::m_Ptr->m_sTexts[LAN_MENU_SETTINGS], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_MENU_SETTINGS]) + "...").c_str());
    ::ModifyMenu(hMenu, IDC_SAVE_SETTINGS, MF_BYCOMMAND, IDC_SAVE_SETTINGS,
        (string(LanguageManager::m_Ptr->m_sTexts[LAN_SAVE_SETTINGS], (size_t)LanguageManager::m_Ptr->m_ui16TextsLens[LAN_SAVE_SETTINGS]) + "...").c_str());
}
//---------------------------------------------------------------------------
 
void MainWindow::EnableStartButton(const BOOL bEnable) const {
    ::EnableWindow((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::BTN_START_STOP], bEnable);
}
//---------------------------------------------------------------------------
 
void MainWindow::SetStartButtonText(const char * sText) const {
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::BTN_START_STOP], sText);
}
//---------------------------------------------------------------------------
 
void MainWindow::SetStatusValue(const char * sText) const {
    ::SetWindowText((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[MainWindowPageStats::LBL_STATUS_VALUE], sText);
}
//---------------------------------------------------------------------------
 
void MainWindow::EnableGuiItems(const BOOL bEnable) const {
    for(uint8_t ui8i = 2; ui8i < 20; ui8i++) {
        ::EnableWindow((reinterpret_cast<MainWindowPageStats *>(m_MainWindowPages[0]))->m_hWndPageItems[ui8i], bEnable);
    }
 
    if(bEnable == FALSE || ::SendMessage((reinterpret_cast<MainWindowPageUsersChat *>(m_MainWindowPages[1]))->m_hWndPageItems[MainWindowPageUsersChat::BTN_AUTO_UPDATE_USERLIST], BM_GETCHECK, 0, 0) == BST_CHECKED) {
        ::EnableWindow((reinterpret_cast<MainWindowPageUsersChat *>(m_MainWindowPages[1]))->m_hWndPageItems[MainWindowPageUsersChat::BTN_UPDATE_USERS], FALSE);
    } else {
        ::EnableWindow((reinterpret_cast<MainWindowPageUsersChat *>(m_MainWindowPages[1]))->m_hWndPageItems[MainWindowPageUsersChat::BTN_UPDATE_USERS], TRUE);
    }
 
    if(bEnable == FALSE) {
        ::SendMessage((reinterpret_cast<MainWindowPageUsersChat *>(m_MainWindowPages[1]))->m_hWndPageItems[MainWindowPageUsersChat::LV_USERS], LVM_DELETEALLITEMS, 0, 0);
        (reinterpret_cast<MainWindowPageScripts *>(m_MainWindowPages[2]))->ClearMemUsageAll();
    }
 
    ::EnableWindow((reinterpret_cast<MainWindowPageScripts *>(m_MainWindowPages[2]))->m_hWndPageItems[MainWindowPageScripts::BTN_RESTART_SCRIPTS], bEnable);
}
//---------------------------------------------------------------------------
 
void MainWindow::OnSelChanged() {
    int iPage = (int)::SendMessage(m_hWndWindowItems[TC_TABS], TCM_GETCURSEL, 0, 0);
 
    if(iPage == -1) {
        return;
    }
 
    TCITEM tcItem = { 0 };
    tcItem.mask = TCIF_PARAM;
 
	if((BOOL)::SendMessage(m_hWndWindowItems[TC_TABS], TCM_GETITEM, iPage, (LPARAM)&tcItem) == FALSE) {
        return;
    }
 
    if(tcItem.lParam == 0) {
        ::MessageBox(m_hWnd, "Not implemented!", g_sPtokaXTitle, MB_OK);
    }
 
    ::BringWindowToTop((reinterpret_cast<MainWindowPage *>(tcItem.lParam))->m_hWnd);
}
//---------------------------------------------------------------------------
 
void MainWindow::SaveGuiSettings() {
    GuiSettingManager::m_Ptr->Save();
};
//---------------------------------------------------------------------------

V773 The function was exited without releasing the 'pAboutDlg' pointer. A memory leak is possible.

V676 It is incorrect to compare the variable of BOOL type with TRUE. Correct expression is: 'bHotTrackEnabled != FALSE'.