/* ----------------------------------------------------------------------
    CRM64Pro GDK - Roberto Prieto
    Copyright (C) 2013-2026 MegaStorm Systems

    This software is provided 'as-is', without any express or implied
    warranty. In no event will the authors be held liable for any damages
    arising from the use of this software.

    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:

    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.

------------------------------------------------------------------------
                        Tutorial 12: GUI
------------------------------------------------------------------------

    Overview:
    This tutorial reuses the background and cursor added to Tutorial.cdc
    by earlier tutorials, then creates a small GUI with panels, labels,
    buttons, text input, a checkbox, a slider and progress.

    Key concepts:
    - Create modeless and modal panels with GUIMgr.
    - Add labels, buttons, text edit, checkbox, slider and progress widgets.
    - Assign normal text and larger title fonts to widgets.
    - Update a progress widget from slider value events.
    - Handle GUI widget events from Main::update().
    - Keep the debug window and console available beside application panels.

    Controls:
    - Mouse: Interact with panels and widgets.
    - Enter: Commit text while the input widget has focus.
    - ESC: Release text input focus.
    - D: Toggle debug window.
    - Grave: Toggle console.
    - Q: Quit.

    Loop order:
    - Process input and GUI widget events.
    - Run one fixed logic update.
    - Let Main::update() call the screen render callback.
    - Let GUIMgr render panels after the screen callback.
------------------------------------------------------------------------ */

#include "CRM64Pro.h"
#include "RunParameter.h"

using namespace CRM64Pro;

#ifdef CRM64PRO_PLATFORM_WINDOWS
#define OUTPUTDIR "Win64/"
#elif defined(CRM64PRO_PLATFORM_LINUX)
#define OUTPUTDIR "Linux/"
#elif defined(CRM64PRO_PLATFORM_MACOS)
#define OUTPUTDIR "macOS/"
#endif

#define RESOURCE_BG_CDC_NAME "Tutorial_bg"
#define RESOURCE_CURSOR_CDC_NAME "Tutorial_cursor"
#define OUTPUT_CDC OUTPUTDIR"Tutorial.cdc"

// Fixed-rate timing.
// Render rate 0 lets the screen render as often as possible. Logic rate controls counters.
static const Sint32 iLogicRate = 20;
static const Sint32 iRenderRate = 0;

// Window size.
static const Sint32 iScreenW = 960;
static const Sint32 iScreenH = 540;

// Widget IDs identify the source of semantic GUI events returned by Main::update().
enum eTutorialWidgetID
{
    WID_LABEL_INTRO = 1,
    WID_LABEL_INPUT,
    WID_LABEL_STATUS,
    WID_TEXT_INPUT,
    WID_BUTTON_APPLY,
    WID_BUTTON_MODAL,
    WID_CHECK_NOTIFICATIONS,
    WID_LABEL_SLIDER,
    WID_SLIDER_VALUE,
    WID_PROGRESS_VALUE,
    WID_LABEL_MODAL,
    WID_BUTTON_CONFIRM,
    WID_BUTTON_CLOSE
};

// Values changed while the tutorial runs.
struct TutorialState
{
    Sint32 idBgImage = -1;
    Sint32 idCursor = -1;
    Sint32 idBuiltInFont = -1;
    Sint32 idTitleFont = -1;
    Sint32 idMainPanel = -1;
    Sint32 idModalPanel = -1;
    Sint32 idDebugWindow = -1;
    float fMouseX = 0.0f;
    float fMouseY = 0.0f;
    Sint32 iCurrentRenderRate = 0;
    Sint32 iCurrentLogicRate = 0;
    Sint32 iLogicFrames = 0;
    Sint32 iWidgetActions = 0;
    Sint32 iSliderValue = 50;
    Sint32 iNotifications = 0;
};

// Helper functions used by the setup log messages.
static void logTaskOk(Log& mLog)
{
    mLog.msg(LL_DEBUG, "OK\n");
}

// Record a failed tutorial task.
static void logTaskFailed(Log& mLog, Sint32 iCode)
{
    mLog.msg(LL_ERROR, "FAILED (Code %d)\n", iCode);
}

// Record a failed tutorial task.
static void logTaskFailed(Log& mLog)
{
    mLog.msg(LL_ERROR, "FAILED\n");
}

// Keep a fixed-rate counter so the debug window can show logic updates beside GUI activity.
static void updateLogic(TutorialState& rState)
{
    rState.iLogicFrames++;
}

// Draw the current frame. GUIMgr draws panels after this callback.
static void renderFrame(Image* pBgImage)
{
    if(pBgImage) pBgImage->render();
}

// Screen render callback.
// Main::update() calls this when the screen is ready to render.
static Sint32 renderWrapper(Sint32, void* pObj)
{
    TutorialState* pState = static_cast<TutorialState*>(pObj);
    if(!pState) return 0;

    Image* pBgImage = Main::instance().imageMgr().get(pState->idBgImage);
    renderFrame(pBgImage);

    return 0;
}

// DebugWindow is a normal GUI panel, so it can coexist with application panels.
static void toggleDebugWindow(DebugWindow* pDebug)
{
    if(!pDebug) return;
    if(pDebug->baseWidget().status() == GS_SHOWN) pDebug->baseWidget().hide();
    else pDebug->baseWidget().show();
}

// Send short GUI feedback to both an application label and the built-in console.
static void setStatus(Panel* pMainPanel, Console* pConsole, const string& rStatus)
{
    if(pMainPanel)
    {
        WidgetLabel* pStatus = pMainPanel->getLabel(WID_LABEL_STATUS);
        if(pStatus) pStatus->setText("Status: " + rStatus);
    }

    if(pConsole) pConsole->print("GUI: %s\n", rStatus.c_str());
}

// Create the tutorial control panel.
static bool createMainPanel(Main& mC64, Log& mLog, TutorialState& rState)
{
    // A modeless panel remains interactive alongside other modeless panels.
    mLog.msg(LL_INFO, "  Create modeless panel ... ");
    rState.idMainPanel = mC64.guiMgr().create("TutorialGUIMain");
    Panel* pPanel = mC64.guiMgr().getPanel(rState.idMainPanel);
    if(rState.idMainPanel < 0 || pPanel == nullptr)
    {
        logTaskFailed(mLog, rState.idMainPanel);
        return false;
    }

    pPanel->baseWidget().setSize(620, 400);
    pPanel->baseWidget().setPosition(PH_CENTER, PH_CENTER);
    // The panel base widget font is used by the title, independently from its children.
    pPanel->baseWidget().setFont(rState.idTitleFont);
    pPanel->baseWidget().setFeatures(WF_DRAGDROP | WF_FADE, true);
    pPanel->setTitle("Tutorial 12: GUI", PH_CENTER, Position(PH_TOP, 12));
    pPanel->setPanelType(PT_MODELESS);

    // Typed create helpers return widgets owned by the panel. Widget IDs are
    // later used to route semantic events without keeping every pointer.
    WidgetLabel* pLabel = pPanel->createLabel("lblIntro", WID_LABEL_INTRO);
    WidgetLabel* pInputLabel = pPanel->createLabel("lblInput", WID_LABEL_INPUT);
    WidgetLabel* pStatus = pPanel->createLabel("lblStatus", WID_LABEL_STATUS);
    WidgetTextEdit* pInput = pPanel->createTextEdit("txtInput", WID_TEXT_INPUT);
    WidgetButton* pApply = pPanel->createButton("btnApply", WID_BUTTON_APPLY);
    WidgetButton* pModal = pPanel->createButton("btnModal", WID_BUTTON_MODAL);
    WidgetCheckBox* pNotifications = pPanel->createCheckBox("chkNotifications", WID_CHECK_NOTIFICATIONS);
    WidgetLabel* pSliderLabel = pPanel->createLabel("lblSlider", WID_LABEL_SLIDER);
    WidgetSlider* pSlider = pPanel->createSlider("sldValue", WID_SLIDER_VALUE, SA_HORIZONTAL);
    WidgetProgress* pProgress = pPanel->createProgress("prgValue", WID_PROGRESS_VALUE);
    if(!pLabel || !pInputLabel || !pStatus || !pInput || !pApply || !pModal ||
        !pNotifications || !pSliderLabel || !pSlider || !pProgress)
    {
        logTaskFailed(mLog);
        return false;
    }

    // Labels use WF_BGDISABLE so only their text is drawn over the panel.
    pLabel->setFont(rState.idBuiltInFont);
    pLabel->setText("Drag this panel while debug and console remain available.");
    pLabel->setPosition(Position(PH_LEFT, 20), Position(PH_TOP, 60));
    pLabel->setFeatures(WF_BGDISABLE, true);

    pInputLabel->setFont(rState.idBuiltInFont);
    pInputLabel->setText("Message:");
    pInputLabel->setPosition(Position(PH_LEFT, 20), Position(PH_TOP, 108));
    pInputLabel->setFeatures(WF_BGDISABLE, true);

    pInput->setFont(rState.idTitleFont);
    pInput->setSize(420, 26);
    pInput->setText("Hello from CRM64Pro GUI");
    pInput->setTextAlign(WTA_CENTER);
    pInput->setPosition(Position(PH_LEFT, 100), Position(PH_TOP, 103));
    // Enter commits the text. ESC or pointer focus loss releases input focus.
    pInput->setFeatures(WF_LOSTFOCUS, true);

    // Buttons emit EC_WIDGET_ACTION when activated.
    pApply->setFont(rState.idTitleFont);
    pApply->setSize(130, 30);
    pApply->setText("Apply message");
    pApply->setPosition(Position(PH_LEFT, 100), Position(PH_TOP, 154));

    pModal->setFont(rState.idTitleFont);
    pModal->setSize(130, 30);
    pModal->setText("Open modal");
    pModal->setPosition(Position(PH_RIGHT, -100), Position(PH_TOP, 154));

    // A checkbox emits EC_WIDGET_TOGGLED with its new state in the event payload.
    pNotifications->setFont(rState.idTitleFont);
    pNotifications->setSize(210, 24);
    pNotifications->setText("Enable notifications");
    pNotifications->setPosition(Position(PH_LEFT, 40), Position(PH_TOP, 214));

    pSliderLabel->setFont(rState.idBuiltInFont);
    pSliderLabel->setText("Slider updates progress:");
    pSliderLabel->setPosition(Position(PH_LEFT, 40), Position(PH_TOP, 264));
    pSliderLabel->setFeatures(WF_BGDISABLE, true);

    // Slider and progress use the same range. The slider is interactive; the
    // progress widget is updated from EC_WIDGET_VALUECHANGED events.
    pSlider->setSize(240, 22);
    pSlider->setRange(0, 100);
    pSlider->setValue(rState.iSliderValue);
    pSlider->setPosition(Position(PH_LEFT, 260), Position(PH_TOP, 260));

    pProgress->setSize(240, 20);
    pProgress->setRange(0, 100);
    pProgress->setValue(rState.iSliderValue);
    pProgress->setPosition(Position(PH_LEFT, 260), Position(PH_TOP, 304));

    pStatus->setFont(rState.idBuiltInFont);
    pStatus->setText("Status: Ready");
    pStatus->setPosition(Position(PH_LEFT, 20), Position(PH_BOTTOM, -20));
    pStatus->setFeatures(WF_BGDISABLE, true);

    // Show the panel and give it keyboard focus for normal interaction.
    pPanel->baseWidget().show();
    mC64.guiMgr().setPanelFocus(rState.idMainPanel);
    logTaskOk(mLog);

    return true;
}

// Create the tutorial modal panel.
static bool createModalPanel(Main& mC64, Log& mLog, TutorialState& rState)
{
    // A modal panel blocks interaction with modeless panels while it is shown.
    mLog.msg(LL_INFO, "  Create modal panel ... ");
    rState.idModalPanel = mC64.guiMgr().create("TutorialGUIModal");
    Panel* pPanel = mC64.guiMgr().getPanel(rState.idModalPanel);
    if(rState.idModalPanel < 0 || pPanel == nullptr)
    {
        logTaskFailed(mLog, rState.idModalPanel);
        return false;
    }

    pPanel->baseWidget().setSize(300, 150);
    pPanel->baseWidget().setPosition(PH_CENTER, PH_CENTER);
    pPanel->baseWidget().setFont(rState.idTitleFont);
    pPanel->setTitle("Modal panel", PH_CENTER, Position(PH_TOP, 12));
    pPanel->setPanelType(PT_MODAL);

    WidgetLabel* pLabel = pPanel->createLabel("lblModal", WID_LABEL_MODAL);
    WidgetButton* pConfirm = pPanel->createButton("btnConfirm", WID_BUTTON_CONFIRM);
    WidgetButton* pClose = pPanel->createButton("btnClose", WID_BUTTON_CLOSE);
    if(!pLabel || !pConfirm || !pClose)
    {
        logTaskFailed(mLog);
        return false;
    }

    pLabel->setFont(rState.idBuiltInFont);
    pLabel->setText("Modal panel blocks other panels.");
    pLabel->setPosition(PH_CENTER, Position(PH_TOP, 54));
    pLabel->setFeatures(WF_BGDISABLE, true);

    pConfirm->setFont(rState.idTitleFont);
    pConfirm->setSize(100, 28);
    pConfirm->setText("Confirm");
    pConfirm->setPosition(Position(PH_LEFT, 30), Position(PH_BOTTOM, -20));

    pClose->setFont(rState.idTitleFont);
    pClose->setSize(100, 28);
    pClose->setText("Close");
    pClose->setPosition(Position(PH_RIGHT, -30), Position(PH_BOTTOM, -20));

    // Keep the modal panel hidden until the modeless panel opens it.
    pPanel->baseWidget().hide();
    logTaskOk(mLog);

    return true;
}

// Run the tutorial application and release its resources.
int main(int argc, char* argv[])
{
    eConfigRendererDriver eRenderer = CRD_SOFTWARE;
    if(!runParameterReadRender(argc, argv, eRenderer)) return 1;

    // Start CRM64Pro memory tracking before creating engine resources.
    // atexit() releases memory tracking data when the program exits.
    CMem::setStatsLevel(CMem::MSL_NORMAL);
    atexit(CMem::destroy);

    // Main gives access to the CRM64Pro managers.
    Main& mC64 = Main::instance();
    Log& mLog = *mC64.logMgr().get();

    TutorialState state;

    // Write log output to stdout and to a file under validation/bin.
    mLog.init("Tutorial_12_GUI", LL_DEBUG, LM_FILE | LM_STDOUT, OUTPUTDIR"Tutorial_12_GUI.log");

    mLog.msg(LL_INFO, "\nSETUP: screen and timing\n");

    // Initialize the timer before calling Main::update().
    // setRate() receives render frames per second, then logic frames per second.
    mLog.msg(LL_INFO, "  Initialize timer ... ");
    if(!mC64.timer().init())
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "  Set frame rates (render=%d, logic=%d) ... ", iRenderRate, iLogicRate);
    if(!mC64.timer().setRate(iRenderRate, iLogicRate))
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    // Add a 1 ms idle delay inside Main::update().
    mC64.configMgr().setMTFriendly(1);

    // Configure and show the default screen.
    Screen* pScreen = mC64.configMgr().get();
    pScreen->setSize(iScreenW, iScreenH);
    pScreen->setDriver(eRenderer);
    pScreen->setTitle("CRM64Pro - Tutorial 12: GUI");

    mLog.msg(LL_INFO, "  Create screen (%dx%d) ... ", iScreenW, iScreenH);
    if(pScreen->show() < 0)
    {
        mLog.msg(LL_CRITICAL, "FAILED\n");
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "  Set render callback ... ");
    if(!pScreen->setRenderCallback([&state](Sint32 iMode) { return renderWrapper(iMode, &state); }))
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "\nRESOURCES: background, cursor and font\n");

    // Earlier tutorials store these reusable resources in Tutorial.cdc.
    mLog.msg(LL_INFO, "  Load background image from CDC ... ");
    state.idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
    if(state.idBgImage < 0)
    {
        logTaskFailed(mLog, state.idBgImage);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "  Load cursor from CDC ... ");
    state.idCursor = mC64.cursorMgr().load(OUTPUT_CDC, RESOURCE_CURSOR_CDC_NAME);
    Cursor* pCursor = mC64.cursorMgr().get(state.idCursor);
    if(state.idCursor < 0 || pCursor == nullptr)
    {
        logTaskFailed(mLog, state.idCursor);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "  Select cursor ... ");
    if(mC64.cursorMgr().select(state.idCursor) < 0 || !mC64.cursorMgr().show())
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    // Child widgets use a compact white font for normal panel content.
    mLog.msg(LL_INFO, "  Load built-in font ... ");
    state.idBuiltInFont = mC64.fontMgr().getBuiltin("CourierNew10White");
    if(state.idBuiltInFont < 0 || mC64.fontMgr().get(state.idBuiltInFont) == nullptr)
    {
        logTaskFailed(mLog, state.idBuiltInFont);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    // Panel base widgets use a larger black font for their title bars.
    mLog.msg(LL_INFO, "  Load title font ... ");
    state.idTitleFont = mC64.fontMgr().getBuiltin("CourierNew10Black");
    if(state.idTitleFont < 0 || mC64.fontMgr().get(state.idTitleFont) == nullptr)
    {
        logTaskFailed(mLog, state.idTitleFont);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "\nGUI: application panels\n");

    // Build application panels before the debug and console tools so all GUI
    // surfaces can be used together during the event loop.
    if(!createMainPanel(mC64, mLog, state) || !createModalPanel(mC64, mLog, state))
    {
        Main::terminate();
        return -1;
    }

    mLog.msg(LL_INFO, "\nTOOLS: debug window and console\n");

    // The DebugWindow is available, but this tutorial starts with it hidden.
    mLog.msg(LL_INFO, "  Create debug window ... ");
    state.idDebugWindow = mC64.guiMgr().create("Debug", CRM64PRO_GUI_DEBUGWINDOW);
    DebugWindow* pDebug = mC64.guiMgr().getDebugWindow(state.idDebugWindow);
    if(!pDebug)
    {
        logTaskFailed(mLog, state.idDebugWindow);
        Main::terminate();
        return -1;
    }
    pDebug->baseWidget().setPosition(Position(PH_RIGHT, -120), Position(PH_TOP, 10));
    pDebug->addWatch("Mouse X", nullptr, &state.fMouseX);
    pDebug->addWatch("Mouse Y", nullptr, &state.fMouseY);
    pDebug->addWatch("Widget actions", &state.iWidgetActions);
    pDebug->addWatch("Render fps", &state.iCurrentRenderRate);
    pDebug->addWatch("Logic fps", &state.iCurrentLogicRate);
    pDebug->addWatch("Logic frames", &state.iLogicFrames);
    pDebug->addWatch("Slider value", &state.iSliderValue);
    pDebug->addWatch("Notifications", &state.iNotifications);
    pDebug->baseWidget().hide();
    logTaskOk(mLog);

    // Main creates the default Console. Log output is also sent there.
    Console* pConsole = mC64.guiMgr().getConsole();
    if(pConsole) pConsole->print("Tutorial 12: GUI ready. Press ` to toggle the console.\n");

    mLog.msg(LL_INFO, "\nCONTROLS\n");
    mLog.msg(LL_INFO, "  Mouse: Interact with panels and widgets\n");
    mLog.msg(LL_INFO, "  [ENTER] Commit text while input has focus\n");
    mLog.msg(LL_INFO, "  [ESC] Release text input focus\n");
    mLog.msg(LL_INFO, "  [D] Toggle debug window\n");
    mLog.msg(LL_INFO, "  [`] Toggle console\n");
    mLog.msg(LL_INFO, "  [Q] Quit\n");
    mLog.msg(LL_INFO, "\nRUNNING\n");

    // Keep borrowed panel pointers for event handlers. GUIMgr retains ownership.
    Panel* pMainPanel = mC64.guiMgr().getPanel(state.idMainPanel);
    Panel* pModalPanel = mC64.guiMgr().getPanel(state.idModalPanel);
    SDL_Event event;
    bool bRunning = true;
    while(bRunning)
    {
        // Main::update() processes input, runs internal GUI logic and calls the
        // screen render callback when a render frame is ready.
        while(mC64.update(&event))
        {
            if(event.type == SDL_EVENT_QUIT)
            {
                bRunning = false;
            }
            else if(event.type == SDL_EVENT_KEY_DOWN)
            {
                // SDL3 stores the key value in event.key.key.
                mLog.msg(LL_INFO, "  Key pressed: %s (%d)\n", SDL_GetKeyName(event.key.key), event.key.key);

                if(event.key.key == SDLK_Q)
                {
                    bRunning = false;
                }
                else if(event.key.key == SDLK_D)
                {
                    toggleDebugWindow(pDebug);
                }
            }
            else if(event.type == ET_C64 &&
                (event.user.code == EC_WIDGET_ACTION || event.user.code == EC_WIDGET_VALUECOMMITTED ||
                event.user.code == EC_WIDGET_TOGGLED || event.user.code == EC_WIDGET_VALUECHANGED))
            {
                // Semantic widget events store the widget ID in data1 and the
                // new checkbox or slider value in data2 when applicable.
                const Sint32 iWidgetID = static_cast<Sint32>(reinterpret_cast<intptr_t>(event.user.data1));
                const Sint32 iWidgetValue = static_cast<Sint32>(reinterpret_cast<intptr_t>(event.user.data2));
                state.iWidgetActions++;

                // Buttons, committed text, checkbox toggles and slider changes
                // all arrive through the same semantic GUI event channel.
                if(iWidgetID == WID_BUTTON_APPLY || iWidgetID == WID_TEXT_INPUT)
                {
                    string sInput;
                    WidgetTextEdit* pInput = pMainPanel ? pMainPanel->getTextEdit(WID_TEXT_INPUT) : nullptr;
                    if(pInput && pInput->getText(sInput))
                    {
                        setStatus(pMainPanel, pConsole, "Message applied");
                        mLog.msg(LL_INFO, "  Message applied: %s\n", sInput.c_str());
                    }
                }
                else if(iWidgetID == WID_BUTTON_MODAL)
                {
                    // Showing a modal panel and assigning focus blocks the modeless panel.
                    if(pModalPanel)
                    {
                        pModalPanel->baseWidget().show();
                        mC64.guiMgr().setPanelFocus(state.idModalPanel);
                    }
                    setStatus(pMainPanel, pConsole, "Modal panel opened");
                }
                else if(iWidgetID == WID_BUTTON_CONFIRM)
                {
                    if(pModalPanel) pModalPanel->baseWidget().hide();
                    setStatus(pMainPanel, pConsole, "Modal action confirmed");
                }
                else if(iWidgetID == WID_BUTTON_CLOSE)
                {
                    if(pModalPanel) pModalPanel->baseWidget().hide();
                    setStatus(pMainPanel, pConsole, "Modal panel closed");
                }
                else if(iWidgetID == WID_CHECK_NOTIFICATIONS)
                {
                    state.iNotifications = iWidgetValue;
                    setStatus(pMainPanel, pConsole, state.iNotifications ? "Notifications enabled" : "Notifications disabled");
                }
                else if(iWidgetID == WID_SLIDER_VALUE)
                {
                    // Mirror the live slider value into the read-only progress display.
                    state.iSliderValue = iWidgetValue;
                    WidgetProgress* pProgress = pMainPanel ? pMainPanel->getProgress(WID_PROGRESS_VALUE) : nullptr;
                    if(pProgress) pProgress->setValue(state.iSliderValue);

                    char szStatus[64];
                    snprintf(szStatus, sizeof(szStatus), "Progress value: %d", state.iSliderValue);
                    setStatus(pMainPanel, pConsole, szStatus);
                }
            }
        }

        if(!bRunning) break;
        state.fMouseX = mC64.cursorMgr().getX();
        state.fMouseY = mC64.cursorMgr().getY();
        state.iCurrentRenderRate = static_cast<Sint32>(mC64.timer().getCurrentRFR());
        state.iCurrentLogicRate = static_cast<Sint32>(mC64.timer().getCurrentLFR());
        updateLogic(state);
    }

    // Close resources owned by this tutorial.
    mC64.guiMgr().info();
    mLog.msg(LL_INFO, "\nCLEANUP\n");
    // Closing a panel also releases the widgets created inside that panel.
    mLog.msg(LL_INFO, "  Close GUI windows ... ");
    mC64.guiMgr().close(state.idDebugWindow);
    mC64.guiMgr().close(state.idModalPanel);
    mC64.guiMgr().close(state.idMainPanel);
    logTaskOk(mLog);
    mLog.msg(LL_INFO, "  Close fonts ... ");
    mC64.fontMgr().close(0);
    logTaskOk(mLog);
    mLog.msg(LL_INFO, "  Close cursors ... ");
    mC64.cursorMgr().close(0);
    logTaskOk(mLog);
    mLog.msg(LL_INFO, "  Close images ... ");
    mC64.imageMgr().close(0);
    logTaskOk(mLog);
    mLog.msg(LL_INFO, "  Close CDC archives ... ");
    mC64.archiveMgr().close(0);
    logTaskOk(mLog);
    Main::terminate();

    return 0;
}
