/*
 * SPDX-FileCopyrightText: 2004 Copyright contributors to the cppTango project
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

#include <tango/windows/w32win.h>

#include <tango/common/versions.h>
#include <tango/server/DeviceImpl.h>
#include <tango/server/DServer.h>
#include <tango/server/Util.h>
#include <tango/windows/resource.h>

namespace Tango
{

static HINSTANCE hInst;
static TCHAR szWindowClass[40] = "TangoDS_Window_Class";
static int Main_Win_width;
static int Main_Win_height;
static HDC Main_Win_MemDC;
static LOGFONT lf;
static LOGFONT user_font;

//+----------------------------------------------------------------------------
//
// method :         W32Win::W32Win()
//
// description :     Constructor of the W32Win class. It mainly creates
//                      the server output main window
//
//-----------------------------------------------------------------------------

W32Win::W32Win(Util *ptr, int nCmd)
{
    HINSTANCE hInstance = GetModuleHandle(nullptr);
    hInst = hInstance;

    RegisterTangoClass(hInstance);

    InitInstance(hInstance, nCmd);

    pcb = new CoutBuf(hInstance, nCmd, win, ptr->get_ds_name().c_str());
}

W32Win::~W32Win() { }

void W32Win::RegisterTangoClass(HINSTANCE hInstance)
{
    WNDCLASS wc;

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC) TangoWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(TANGO_ICON1));
    wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName = (LPCSTR) TANGO_MENU1;
    wc.lpszClassName = szWindowClass;

    ATOM at = RegisterClass(&wc);
    if(at == 0)
    {
        TANGO_THROW_EXCEPTION((LPCSTR) API_NtDebugWindowError,
                              (LPCSTR) "Can't register class for server main output window");
    }
}

void W32Win::InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd;

    //
    // Build window title
    //

    TCHAR szTitle[80];
    strcpy(szTitle, "Tango device server : ");
    Tango::Util *tg = Tango::Util::instance();
    strcat(szTitle, tg->get_ds_name().c_str());

    //
    // Create window
    //

    hWnd = CreateWindow(szWindowClass,
                        szTitle,
                        WS_OVERLAPPEDWINDOW,
                        CW_USEDEFAULT,
                        0,
                        CW_USEDEFAULT,
                        0,
                        nullptr,
                        nullptr,
                        hInstance,
                        nullptr);

    if(!hWnd)
    {
        TANGO_THROW_EXCEPTION((LPCSTR) API_NtDebugWindowError, (LPCSTR) "Can't create main device server window");
    }
    win = hWnd;

    //
    // Set background bitmap
    //

    HBITMAP dancers = LoadBitmap(hInst, MAKEINTRESOURCE(TANGO_BITMAP1));
    BITMAP dancers_bm;
    GetObject(dancers, sizeof(BITMAP), &dancers_bm);

    HDC dc = GetDC(hWnd);
    HDC mem_dc = CreateCompatibleDC(dc);
    SelectObject(mem_dc, dancers);

    RECT wnd, client, calc;
    GetWindowRect(hWnd, &wnd);
    GetClientRect(hWnd, &client);
    calc.right = wnd.right - wnd.left - client.right + dancers_bm.bmWidth;
    calc.bottom = wnd.bottom - wnd.top - client.bottom + dancers_bm.bmHeight;

    //
    // Set window size
    //

    SetWindowPos(
        hWnd, HWND_TOP, 0, 0, (int) (calc.right * X_FACTOR), (int) (calc.bottom * Y_FACTOR), SWP_NOMOVE | SWP_NOZORDER);

    //    SetBkMode(mem_dc,TRANSPARENT);
    SetBkMode(mem_dc, (int) 1);

    Main_Win_width = dancers_bm.bmWidth;
    Main_Win_height = dancers_bm.bmHeight;
    Main_Win_MemDC = mem_dc;

    //
    // Release display context
    //

    ReleaseDC(hWnd, dc);

    //
    // Display window
    //

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
}

//
// Window procedure for debug window
//

LRESULT CALLBACK TangoWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId;
    PAINTSTRUCT ps;
    HDC hdc;
    BOOL bo;
    HMENU hMenu;
    LogLevel log_level;
    RECT re;
    int calc_hei;
    bool too_large = false;
    Tango::Util *tg = Tango::Util::instance();

    switch(message)
    {
    case WM_COMMAND:
        wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch(wmId)
        {
        case TANGO_APP_ABOUT:
            DialogBox(hInst, (LPCTSTR) TANGO_ABOUT1, hWnd, (DLGPROC) AboutProc);
            break;

        case ID_FILE_EXIT:
            DestroyWindow(hWnd);
            break;

        case ID_VIEW_CONSOLE:
            hMenu = GetMenu(hWnd);
            if(tg->get_console_window() == nullptr)
            {
                tg->get_debug_object()->CreateWin(tg->get_ds_name().c_str());
            }

            MENUITEMINFO mii;

            mii.fMask = MIIM_STATE;
            mii.cbSize = sizeof(mii);
            bo = GetMenuItemInfo(hMenu, ID_VIEW_CONSOLE, FALSE, &mii);
            if(mii.fState == 0x0)
            {
                ShowWindow(tg->get_console_window(), SW_SHOW);
            }
            else
            {
                ShowWindow(tg->get_console_window(), SW_HIDE);
            }

            mii.fState ^= MFS_CHECKED;
            bo = SetMenuItemInfo(hMenu, ID_VIEW_CONSOLE, FALSE, &mii);
            break;

        case ID_DEBUG_LEVEL0:
            SetDebugLevel(LOG_OFF, GetMenu(hWnd), true);
            tg->set_trace_level(0);
            Logging::get_core_logger()->set_level(log4tango::Level::OFF);
            break;

        case ID_DEBUG_LEVEL1:
            SetDebugLevel(LOG_FATAL, GetMenu(hWnd), true);
            tg->set_trace_level(1);
            Logging::get_core_logger()->set_level(log4tango::Level::FATAL);
            add_cout_appender(tg);
            break;

        case ID_DEBUG_LEVEL2:
            SetDebugLevel(LOG_ERROR, GetMenu(hWnd), true);
            tg->set_trace_level(2);
            Logging::get_core_logger()->set_level(log4tango::Level::ERROR);
            add_cout_appender(tg);
            break;

        case ID_DEBUG_LEVEL3:
            SetDebugLevel(LOG_WARN, GetMenu(hWnd), true);
            tg->set_trace_level(3);
            Logging::get_core_logger()->set_level(log4tango::Level::WARN);
            add_cout_appender(tg);
            break;

        case ID_DEBUG_LEVEL4:
            SetDebugLevel(LOG_INFO, GetMenu(hWnd), true);
            tg->set_trace_level(4);
            Logging::get_core_logger()->set_level(log4tango::Level::INFO);
            add_cout_appender(tg);
            break;

        case ID_DEBUG_LEVEL5:
            SetDebugLevel(LOG_DEBUG, GetMenu(hWnd), true);
            tg->set_trace_level(4);
            Logging::get_core_logger()->set_level(log4tango::Level::DEBUG);
            add_cout_appender(tg);
            break;

        case ID_DEV_DEBUG_LEVEL0:
            SetDebugLevel(LOG_OFF, GetMenu(hWnd), false);
            all_dev(tg, LOG_OFF);
            break;

        case ID_DEV_DEBUG_LEVEL1:
            SetDebugLevel(LOG_FATAL, GetMenu(hWnd), false);
            all_dev(tg, LOG_FATAL);
            all_dev_add_cout(tg);
            break;

        case ID_DEV_DEBUG_LEVEL2:
            SetDebugLevel(LOG_ERROR, GetMenu(hWnd), false);
            all_dev(tg, LOG_ERROR);
            all_dev_add_cout(tg);
            break;

        case ID_DEV_DEBUG_LEVEL3:
            SetDebugLevel(LOG_WARN, GetMenu(hWnd), false);
            all_dev(tg, LOG_WARN);
            all_dev_add_cout(tg);
            break;

        case ID_DEV_DEBUG_LEVEL4:
            SetDebugLevel(LOG_INFO, GetMenu(hWnd), false);
            all_dev(tg, LOG_INFO);
            all_dev_add_cout(tg);
            break;

        case ID_DEV_DEBUG_LEVEL5:
            SetDebugLevel(LOG_DEBUG, GetMenu(hWnd), false);
            all_dev(tg, LOG_DEBUG);
            all_dev_add_cout(tg);
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        break;

    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        BitBlt(hdc, 0, 0, Main_Win_width, Main_Win_height, Main_Win_MemDC, 0, 0, SRCCOPY);

        //
        // Set font for the main text
        //

        strcpy(lf.lfFaceName, "French Script MT");
        lf.lfWeight = FW_BOLD;
        lf.lfHeight = -40;
        lf.lfCharSet = DEFAULT_CHARSET;
        SelectObject(hdc, CreateFontIndirect(&lf));

        //
        // Display text (in red)
        //

        SetTextColor(hdc, RGB(255, 0, 0));
        re.left = Main_Win_width;
        re.top = 0;
        re.right = Main_Win_width * 2;
        re.bottom = Main_Win_height;
        calc_hei = DrawText(hdc, TEXT(MAIN_TXT), -1, &re, DT_CALCRECT | DT_CENTER);
        re.top = (Main_Win_height - calc_hei) / 2;
        re.bottom = (Main_Win_height + calc_hei) / 2;
        re.right = Main_Win_width * 2;
        re.left = Main_Win_width;
        DrawText(hdc, TEXT(MAIN_TXT), -1, &re, DT_CENTER);

        //
        // Change font to a more classical one
        //

        user_font.lfCharSet = DEFAULT_CHARSET;
        SelectObject(hdc, CreateFontIndirect(&user_font));

        //
        // Display user text. If the text is too large to fit into the foreseen place
        // replace it with a predefined message
        //

        re.top = Main_Win_height;
        re.left = 0;
        re.right = (int) (Main_Win_width * X_FACTOR);
        re.bottom = (int) (Main_Win_height * Y_FACTOR);
        SetTextColor(hdc, RGB(0, 0, 255));
        calc_hei = DrawText(hdc, tg->get_main_window_text().c_str(), -1, &re, DT_CALCRECT | DT_CENTER);
        if(calc_hei > ((Y_FACTOR - 1) * Main_Win_height))
        {
            too_large = true;
            re.top = Main_Win_height;
            re.bottom = (int) (Main_Win_height * Y_FACTOR);
        }
        else
        {
            re.top = (int) (Main_Win_height + (Main_Win_height * ((Y_FACTOR - 1) / 2)) - (calc_hei / 2));
            re.bottom = (int) (Main_Win_height + (Main_Win_height * ((Y_FACTOR - 1) / 2)) + (calc_hei / 2));
        }
        re.left = 0;
        re.right = Main_Win_width * X_FACTOR;

        if(too_large == true)
        {
            DrawText(hdc, TOO_LARGE_TXT, -1, &re, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
        }
        else
        {
            DrawText(hdc, tg->get_main_window_text().c_str(), -1, &re, DT_CENTER);
        }

        log_level = Logging::log4tango_to_tango_level(Logging::get_core_logger()->get_level());
        SetDebugLevel(log_level, GetMenu(hWnd), true);
        if(get_all_dev_log_level(tg, log_level) == 0)
        {
            SetDebugLevel(log_level, GetMenu(hWnd), false);
        }
        else
        {
            NoDebugLevel(GetMenu(hWnd), false);
        }
        EndPaint(hWnd, &ps);
        break;

    case WM_DESTROY:
        //        tg->get_dserver_device()->DServer::~DServer();
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

void SetDebugLevel(LogLevel level, HMENU menu, bool admin_dev)
{
    MENUITEMINFO mii;
    BOOL bo;

    //
    // Reset all items
    //

    mii.fMask = MIIM_STATE;
    mii.fState = 0;
    mii.cbSize = sizeof(mii);

    if(admin_dev == true)
    {
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL0, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL1, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL2, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL3, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL4, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL5, FALSE, &mii);
    }
    else
    {
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL0, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL1, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL2, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL3, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL4, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL5, FALSE, &mii);
    }

    //
    // Set the chosen level
    //

    mii.fState = MFS_CHECKED;
    switch(level)
    {
    case LOG_OFF:
        if(admin_dev == true)
        {
            bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL0, FALSE, &mii);
        }
        else
        {
            bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL0, FALSE, &mii);
        }
        break;

    case LOG_FATAL:
        if(admin_dev == true)
        {
            bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL1, FALSE, &mii);
        }
        else
        {
            bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL1, FALSE, &mii);
        }
        break;

    case LOG_ERROR:
        if(admin_dev == true)
        {
            bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL2, FALSE, &mii);
        }
        else
        {
            bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL2, FALSE, &mii);
        }
        break;

    case LOG_WARN:
        if(admin_dev == true)
        {
            bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL3, FALSE, &mii);
        }
        else
        {
            bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL3, FALSE, &mii);
        }
        break;

    case LOG_INFO:
        if(admin_dev == true)
        {
            bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL4, FALSE, &mii);
        }
        else
        {
            bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL4, FALSE, &mii);
        }
        break;

    case LOG_DEBUG:
        if(admin_dev == true)
        {
            bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL5, FALSE, &mii);
        }
        else
        {
            bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL5, FALSE, &mii);
        }
        break;
    }
}

void NoDebugLevel(HMENU menu, bool admin_dev)
{
    MENUITEMINFO mii;
    BOOL bo;

    //
    // Reset all items
    //

    mii.fMask = MIIM_STATE;
    mii.fState = 0;
    mii.cbSize = sizeof(mii);

    if(admin_dev == true)
    {
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL0, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL1, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL2, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL3, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL4, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEBUG_LEVEL5, FALSE, &mii);
    }
    else
    {
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL0, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL1, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL2, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL3, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL4, FALSE, &mii);
        bo = SetMenuItemInfo(menu, ID_DEV_DEBUG_LEVEL5, FALSE, &mii);
    }
}

LRESULT CALLBACK AboutProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    Util *tg = Util::instance();
    TCHAR str[160];

    switch(message)
    {
    case WM_INITDIALOG:
        strcpy(str, "Device server : ");
        strcat(str, tg->get_ds_name().c_str());
        SetDlgItemText(hDlg, TANGO_ABOUT_TEXT2, (LPCSTR) str);

        std::snprintf(str,
                      sizeof(str),
                      "TANGO release : %d.%d.%d.",
                      TANGO_VERSION_MAJOR,
                      TANGO_VERSION_MINOR,
                      TANGO_VERSION_PATCH);
        SetDlgItemText(hDlg, TANGO_ABOUT_TEXT3, (LPCSTR) str);

        strcpy(str, "TANGO IDL definition release : ");
        strcat(str, tg->get_version_str().c_str());
        SetDlgItemText(hDlg, TANGO_ABOUT_TEXT4, (LPCSTR) str);

        strcpy(str, "Server release : ");
        strcat(str, tg->get_server_version().c_str());
        SetDlgItemText(hDlg, TANGO_ABOUT_TEXT5, (LPCSTR) str);
        return TRUE;

    case WM_COMMAND:
        if(LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return TRUE;
        }
        break;
    }
    return FALSE;
}

void all_dev(Util *tg, LogLevel level)
{
    std::vector<DeviceImpl *> dev_list;

    dev_list = tg->get_device_list("*");
    for(unsigned int i = 0; i < dev_list.size(); i++)
    {
        std::string dev_name = dev_list[i]->get_name();
        if(dev_name.size() > 7)
        {
            std::string dev_name_begin = dev_name.substr(0, 7);
            transform(dev_name_begin.begin(), dev_name_begin.end(), dev_name_begin.begin(), ::tolower);
            if(dev_name_begin == "dserver")
            {
                continue;
            }
        }
        log4tango::Level::Value log4tango_level = Logging::tango_to_log4tango_level(level);
        dev_list[i]->get_logger()->set_level(log4tango_level);
    }
}

int get_all_dev_log_level(Util *tg, LogLevel &lev)
{
    std::vector<DeviceImpl *> dev_list;
    LogLevel ini_log_level = LOG_WARN;
    LogLevel log_level;
    int i, j;

    dev_list = tg->get_device_list("*");
    int nb_dev = dev_list.size();

    //
    // If not fully initialized yet
    //
    if(nb_dev == 0)
    {
        return -1;
    }

    //
    // A loop on each device but forget admin device
    //

    for(i = 0, j = 0; i < nb_dev; i++)
    {
        std::string dev_name = dev_list[i]->get_name();
        if(dev_name.size() > 7)
        {
            std::string dev_name_begin = dev_name.substr(0, 7);
            transform(dev_name_begin.begin(), dev_name_begin.end(), dev_name_begin.begin(), ::tolower);
            if(dev_name_begin == "dserver")
            {
                continue;
            }
        }
        j++;
        if(j == 1)
        {
            ini_log_level = Logging::log4tango_to_tango_level(dev_list[i]->get_logger()->get_level());
        }
        else
        {
            log_level = Logging::log4tango_to_tango_level(dev_list[i]->get_logger()->get_level());
            if(log_level != ini_log_level)
            {
                return -1;
            }
        }
    }

    //
    // If only one device (the admin one)
    //

    if((i == 1) && (j == 0))
    {
        return -1;
    }

    lev = ini_log_level;
    return 0;
}

void add_cout_appender(Util *tg)
{
    int i, nb_appenders;
    Tango::DevVarStringArray *log_target;

    try
    {
        log_target = Logging::get_logging_target(tg->get_dserver_device()->get_name());
        nb_appenders = log_target->length();
        if(nb_appenders == 0)
        {
            Logging::add_logging_target(Logging::get_core_logger(), "console::cout", false);
        }
        else
        {
            for(i = 0; i < nb_appenders; i++)
            {
                if(::strcmp((*log_target)[i].in(), "console::cout") == 0)
                {
                    break;
                }
            }
            if(i == nb_appenders)
            {
                Logging::add_logging_target(Logging::get_core_logger(), "console::cout", false);
            }
        }
        delete log_target;
    }
    catch(...)
    {
    }
}

void all_dev_add_cout(Util *tg)
{
    std::vector<DeviceImpl *> dev_list;
    int j, nb_appenders;
    Tango::DevVarStringArray *log_target;

    dev_list = tg->get_device_list("*");
    for(unsigned int i = 0; i < dev_list.size(); i++)
    {
        std::string dev_name = dev_list[i]->get_name();
        if(dev_name.size() > 7)
        {
            std::string dev_name_begin = dev_name.substr(0, 7);
            transform(dev_name_begin.begin(), dev_name_begin.end(), dev_name_begin.begin(), ::tolower);
            if(dev_name_begin == "dserver")
            {
                continue;
            }
        }
        try
        {
            log_target = Logging::get_logging_target(dev_list[i]->get_name());
            nb_appenders = log_target->length();
            if(nb_appenders == 0)
            {
                Logging::add_logging_target(dev_list[i]->get_logger(), "console::cout", false);
            }
            else
            {
                for(j = 0; j < nb_appenders; j++)
                {
                    if(::strcmp((*log_target)[j].in(), "console::cout") == 0)
                    {
                        break;
                    }
                }
                if(j == nb_appenders)
                {
                    Logging::add_logging_target(dev_list[i]->get_logger(), "console::cout", false);
                }
            }
            delete log_target;
        }
        catch(...)
        {
        }
    }
}

} // namespace Tango
