/* ----------------------------------------------------------------------
    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 06: GFX
------------------------------------------------------------------------

    Overview:
    This tutorial extends Tutorial 05 Font with GFX drawing primitives,
    image filters and glow helpers.
    It keeps the runtime resources loaded from Tutorial.cdc and uses a built-in
    font only for the render and logic rate overlay.

    Key concepts:
    - Access the GFX interface through Main::gfx().
    - Draw lines, rectangles, circles, ellipses and polygons.
    - Apply Image filters to duplicate images.
    - Draw additive glow helpers.
    - Mix image rendering and immediate GFX drawing in the same render callback.
    - Read a pixel color at the mouse position.
    - Keep the debug window available but hidden by default.

    Controls:
    - Cursor Keys: Move the ship image.
    - G: Read the pixel color under the cursor.
    - D: Toggle debug window.
    - Grave: Toggle console.
    - Any Key/Mouse: Logs events.
    - Q / ESC: Quit.

    Loop order:
    - Process input events.
    - Run one fixed logic update.
    - Let Main::update() call the screen render 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_PLAYER_CDC_NAME "Tutorial_ship"
#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 movement.
static const Sint32 iLogicRate = 20;
static const Sint32 iRenderRate = 0;

// Window size and movement speed.
static const Sint32 iScreenW = 960;
static const Sint32 iScreenH = 540;
static const float fMoveSpeed = 220.0f;

// Values changed while the tutorial runs.
struct TutorialState
{
    Sint32 idBgImage = -1;
    Sint32 idPlayerImage = -1;
    Sint32 idFilterGrey = -1;
    Sint32 idFilterNegative = -1;
    Sint32 idFilterPixelate = -1;
    Sint32 idCursor = -1;
    Sint32 idBuiltInFont = -1;
    Sint32 idDebugWindow = -1;
    float fPlayerX = 386.0f;
    float fPlayerY = 186.0f;
    float fMouseX = 0.0f;
    float fMouseY = 0.0f;
    float fMarkerX = 820.0f;
    float fMarkerY = 250.0f;
    Sint32 iCurrentRenderRate = 0;
    Sint32 iCurrentLogicRate = 0;
    Sint32 iLogicFrames = 0;
    Uint32 iLastPixel = 0;
    bool bMoveLeft = false;
    bool bMoveRight = false;
    bool bMoveUp = false;
    bool bMoveDown = false;
};

// Keep a value inside the given range.
static float clampFloat(float fValue, float fMin, float fMax)
{
    if(fValue < fMin) return fMin;
    if(fValue > fMax) return fMax;
    return fValue;
}

// 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");
}

// Move the ship image one fixed logic step.
static void updateLogic(TutorialState& rState, Image* pPlayerImage, float fDeltaTime)
{
    if(!pPlayerImage) return;

    float fDX = 0.0f;
    float fDY = 0.0f;

    if(rState.bMoveLeft) fDX -= fMoveSpeed * fDeltaTime;
    if(rState.bMoveRight) fDX += fMoveSpeed * fDeltaTime;
    if(rState.bMoveUp) fDY -= fMoveSpeed * fDeltaTime;
    if(rState.bMoveDown) fDY += fMoveSpeed * fDeltaTime;

    const float fMaxX = static_cast<float>(iScreenW - pPlayerImage->getWidth());
    const float fMaxY = static_cast<float>(iScreenH - pPlayerImage->getHeight());
    rState.fPlayerX = clampFloat(rState.fPlayerX + fDX, 0.0f, fMaxX);
    rState.fPlayerY = clampFloat(rState.fPlayerY + fDY, 0.0f, fMaxY);
    rState.iLogicFrames++;
}

// Draw one row of graphics primitive samples.
static void renderPrimitiveRow(float fOffsetY, Uint8 iAlpha)
{
    GFX& mGFX = Main::instance().gfx();

    // Lines.
    mGFX.lineH(40, 240, 60 + fOffsetY, 255, 220, 80, iAlpha);
    mGFX.lineV(40, 60 + fOffsetY, 170 + fOffsetY, 255, 220, 80, iAlpha);
    mGFX.line(40, 170 + fOffsetY, 240, 60 + fOffsetY, 255, 255, 255, iAlpha);
    mGFX.line(40, 60 + fOffsetY, 240, 170 + fOffsetY, 0, 210, 255, iAlpha);

    // Rectangles.
    mGFX.rect(290, 55 + fOffsetY, 405, 145 + fOffsetY, 0, 230, 255, iAlpha);
    mGFX.rectFilled(315, 80 + fOffsetY, 380, 120 + fOffsetY, 0, 170, 90, iAlpha);
    mGFX.rectRound(430, 55 + fOffsetY, 555, 145 + fOffsetY, 18, 255, 120, 80, iAlpha);
    mGFX.rectFilledRound(460, 85 + fOffsetY, 525, 125 + fOffsetY, 14, 255, 120, 80, iAlpha);

    // Circles and ellipses.
    mGFX.circle(665, 100 + fOffsetY, 50, 150, 255, 120, iAlpha);
    mGFX.circleFilled(665, 100 + fOffsetY, 24, 150, 255, 120, iAlpha);
    mGFX.ellipse(790, 100 + fOffsetY, 58, 35, 255, 255, 120, iAlpha);
    mGFX.ellipseFilled(790, 100 + fOffsetY, 28, 16, 255, 255, 120, iAlpha);
}

// Draw the graphics tutorial samples.
static void renderGFX(const TutorialState& rState)
{
    GFX& mGFX = Main::instance().gfx();

    // First row: opaque primitives. Second row: same primitives with alpha.
    renderPrimitiveRow(0.0f, 255);
    renderPrimitiveRow(145.0f, 120);

    // Polygon outline and filled polygon.
    float fPolyX[5] = { 80.0f, 130.0f, 210.0f, 180.0f, 95.0f };
    float fPolyY[5] = { 405.0f, 335.0f, 375.0f, 475.0f, 470.0f };
    float fFillX[4] = { 310.0f, 430.0f, 500.0f, 360.0f };
    float fFillY[4] = { 395.0f, 330.0f, 455.0f, 490.0f };
    mGFX.polygon(fPolyX, fPolyY, 5, 255, 255, 255, 255);
    mGFX.polygonFilled(fFillX, fFillY, 4, 0, 210, 255, 140);

    // Marker uses pixel and circle drawing. Move it by clicking or pressing G.
    mGFX.circleFilled(rState.fMarkerX, rState.fMarkerY, 6, 255, 80, 80, 255);
    mGFX.lineH(rState.fMarkerX - 14.0f, rState.fMarkerX + 14.0f, rState.fMarkerY, 255, 80, 80, 255);
    mGFX.lineV(rState.fMarkerX, rState.fMarkerY - 14.0f, rState.fMarkerY + 14.0f, 255, 80, 80, 255);
    mGFX.setPixel(rState.fMarkerX, rState.fMarkerY, 255, 255, 255, 255);

    // Additive helpers draw decorative light effects over the scene.
    mGFX.drawGlow(610, 340, 58, 80, 180, 255, 160, 0.80f);
    mGFX.drawRadialGlow(735, 340, 12, 68, 255, 100, 220, 170, 0.85f);
    mGFX.drawFlash(860, 340, 54, 255, 235, 120, 210, 1.0f);
}

// Draw text at the requested screen position.
static void renderText(Font* pBuiltInFont, Sint32 iX, Sint32 iY, const char* szText)
{
    if(!pBuiltInFont) return;
    pBuiltInFont->setPosition(iX, iY);
    pBuiltInFont->render(szText);
}

// Draw the image filtering examples.
static void renderFilterSamples(Font* pBuiltInFont, const TutorialState& rState)
{
    Main& mC64 = Main::instance();
    const Sint32 iFilters[3] = { rState.idFilterGrey, rState.idFilterNegative, rState.idFilterPixelate };
    const char* szLabels[3] = { "Grey", "Negative", "Pixelate" };

    for(Sint32 i = 0; i < 3; ++i)
    {
        Image* pImage = mC64.imageMgr().get(iFilters[i]);
        if(!pImage) continue;

        SDL_FRect rDst = {
            600.0f + static_cast<float>(i * 115),
            450.0f,
            80.0f,
            80.0f
        };
        pImage->render(0, nullptr, &rDst);
        renderText(pBuiltInFont, static_cast<Sint32>(rDst.x), 432, szLabels[i]);
    }
}

// Draw the current timing information.
static void renderRates(Font* pBuiltInFont, const TutorialState& rState)
{
    if(!pBuiltInFont) return;

    char szRates[96];
    snprintf(szRates, sizeof(szRates), "Render: %d fps   Logic: %d fps", rState.iCurrentRenderRate, rState.iCurrentLogicRate);
    renderText(pBuiltInFont, 10, 10, szRates);

}

// Draw the current frame. CursorMgr draws the selected cursor automatically.
static void renderFrame(Image* pBgImage, Image* pPlayerImage, Font* pBuiltInFont, const TutorialState& rState)
{
    if(pBgImage) pBgImage->render();

    renderGFX(rState);

    if(pPlayerImage)
    {
        SDL_FRect rDst = {
            rState.fPlayerX,
            rState.fPlayerY,
            static_cast<float>(pPlayerImage->getWidth()),
            static_cast<float>(pPlayerImage->getHeight())
        };
        Main::instance().gfx().drawGlowImage(rState.idPlayerImage, rState.fPlayerX + 32.0f, rState.fPlayerY + 32.0f, 1.35f, 255, 180, 70, 130, 0.70f);
        pPlayerImage->render(0, nullptr, &rDst);
    }

    renderFilterSamples(pBuiltInFont, rState);
    renderRates(pBuiltInFont, rState);
}

// Create an image resource with the selected filter.
static bool createFilteredImage(Main& mC64, Log& mLog, Sint32 idSource, const char* szName, eImageFilter eFilter, Sint32 iValue, Sint32& rID)
{
    mLog.msg(LL_INFO, "  Create %s filter sample ... ", szName);
    rID = mC64.imageMgr().duplicate(idSource, szName);
    Image* pImage = mC64.imageMgr().get(rID);
    if(rID < 0 || pImage == nullptr)
    {
        logTaskFailed(mLog, rID);
        return false;
    }

    const Sint32 iRet = pImage->applyFilter(eFilter, iValue);
    if(iRet < 0)
    {
        logTaskFailed(mLog, iRet);
        mC64.imageMgr().close(rID);
        rID = -1;
        return false;
    }
    logTaskOk(mLog);
    return true;
}

// 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;

    Main& mC64 = Main::instance();
    Image* pBgImage = mC64.imageMgr().get(pState->idBgImage);
    Image* pPlayerImage = mC64.imageMgr().get(pState->idPlayerImage);
    Font* pBuiltInFont = mC64.fontMgr().get(pState->idBuiltInFont);
    renderFrame(pBgImage, pPlayerImage, pBuiltInFont, *pState);

    return 0;
}

// Show or hide the debug window.
static void toggleDebugWindow(DebugWindow* pDebug)
{
    if(!pDebug) return;
    if(pDebug->baseWidget().status() == GS_SHOWN) pDebug->baseWidget().hide();
    else pDebug->baseWidget().show();
}

// 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_06_GFX", LL_DEBUG, LM_FILE | LM_STDOUT, OUTPUTDIR"Tutorial_06_GFX.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 06: GFX");

    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: load from CDC\n");

    // Tutorial 06 does not add new resources. It uses the CDC built by previous tutorials.
    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 ship image from CDC ... ");
    state.idPlayerImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_PLAYER_CDC_NAME);
    if(state.idPlayerImage < 0)
    {
        logTaskFailed(mLog, state.idPlayerImage);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    Image* pPlayerImage = mC64.imageMgr().get(state.idPlayerImage);
    if(pPlayerImage == nullptr)
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }

    mLog.msg(LL_INFO, "\nFILTERS: duplicate ship and apply filters\n");
    if(!createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterGrey", IF_GREY, 0, state.idFilterGrey) ||
        !createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterNegative", IF_NEGATIVE, 0, state.idFilterNegative) ||
        !createFilteredImage(mC64, mLog, state.idPlayerImage, "TutorialFilterPixelate", IF_PIXELATE, 8, state.idFilterPixelate))
    {
        Main::terminate();
        return -1;
    }

    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);

    mLog.msg(LL_INFO, "  Load built-in font ... ");
    state.idBuiltInFont = mC64.fontMgr().getBuiltin("CourierNew10White");
    Font* pBuiltInFont = mC64.fontMgr().get(state.idBuiltInFont);
    if(state.idBuiltInFont < 0 || pBuiltInFont == nullptr)
    {
        logTaskFailed(mLog, state.idBuiltInFont);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    mLog.msg(LL_INFO, "\nTOOLS: GFX, glow helpers, debug window and console\n");

    // Main::gfx() returns primitives, pixel helpers and glow helpers. No resource creation is required.
    mLog.msg(LL_INFO, "  Access GFX interface ... ");
    GFX& mGFX = mC64.gfx();
    (void)mGFX;
    logTaskOk(mLog);

    // 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("Player X", nullptr, &state.fPlayerX);
    pDebug->addWatch("Player Y", nullptr, &state.fPlayerY);
    pDebug->addWatch("Mouse X", nullptr, &state.fMouseX);
    pDebug->addWatch("Mouse Y", nullptr, &state.fMouseY);
    pDebug->addWatch("Marker X", nullptr, &state.fMarkerX);
    pDebug->addWatch("Marker Y", nullptr, &state.fMarkerY);
    pDebug->addWatch("Render fps", &state.iCurrentRenderRate);
    pDebug->addWatch("Logic fps", &state.iCurrentLogicRate);
    pDebug->addWatch("Logic frames", &state.iLogicFrames);
    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 06: GFX ready. Press ` to toggle the console.\n");

    mLog.msg(LL_INFO, "\nCONTROLS\n");
    mLog.msg(LL_INFO, "  [Q] / [ESC] Quit\n");
    mLog.msg(LL_INFO, "  [Arrow Keys] Move the ship image\n");
    mLog.msg(LL_INFO, "  [G] Read pixel color under the cursor\n");
    mLog.msg(LL_INFO, "  [D] Toggle debug window\n");
    mLog.msg(LL_INFO, "  [`] Toggle console\n");
    mLog.msg(LL_INFO, "  Any key or mouse button logs the input event\n");
    mLog.msg(LL_INFO, "\nRUNNING\n");

    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 || event.key.key == SDLK_ESCAPE)
                {
                    bRunning = false;
                }
                else if(event.key.key == SDLK_LEFT)
                {
                    state.bMoveLeft = true;
                }
                else if(event.key.key == SDLK_RIGHT)
                {
                    state.bMoveRight = true;
                }
                else if(event.key.key == SDLK_UP)
                {
                    state.bMoveUp = true;
                }
                else if(event.key.key == SDLK_DOWN)
                {
                    state.bMoveDown = true;
                }
                else if(event.key.key == SDLK_G)
                {
                    state.fMarkerX = mC64.cursorMgr().getX();
                    state.fMarkerY = mC64.cursorMgr().getY();
                    state.iLastPixel = mC64.gfx().getPixel(state.fMarkerX, state.fMarkerY);
                    mLog.msg(LL_INFO, "  Pixel at %.0f, %.0f: %#08x\n", state.fMarkerX, state.fMarkerY, state.iLastPixel);
                }
                else if(event.key.key == SDLK_D)
                {
                    toggleDebugWindow(pDebug);
                }
            }
            else if(event.type == SDL_EVENT_KEY_UP)
            {
                if(event.key.key == SDLK_LEFT) state.bMoveLeft = false;
                else if(event.key.key == SDLK_RIGHT) state.bMoveRight = false;
                else if(event.key.key == SDLK_UP) state.bMoveUp = false;
                else if(event.key.key == SDLK_DOWN) state.bMoveDown = false;
            }
            else if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN)
            {
                state.fMarkerX = event.button.x;
                state.fMarkerY = event.button.y;
                state.iLastPixel = mC64.gfx().getPixel(state.fMarkerX, state.fMarkerY);
                mLog.msg(LL_INFO, "  Mouse clicked: button %d at %d, %d; pixel %#08x\n", event.button.button, (Sint32)event.button.x, (Sint32)event.button.y, state.iLastPixel);
            }
        }

        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, pPlayerImage, 1.0f / static_cast<float>(iLogicRate));
    }

    // Close resources owned by this tutorial.
    mC64.gfx().info();
    mLog.msg(LL_INFO, "\nCLEANUP\n");
    mLog.msg(LL_INFO, "  Close debug window ... ");
    mC64.guiMgr().close(state.idDebugWindow);
    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;
}
