/* ----------------------------------------------------------------------
    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 03: Configuration
------------------------------------------------------------------------

    Overview:
    This tutorial extends Tutorial 02 System with the built-in configuration
    screen. It stores config.xml in Tutorial.cdc beside the existing image
    resources, then demonstrates saving and loading the active configuration.

    Key concepts:
    - Open the built-in ConfigMgr setup screen.
    - Save the current audio and screen configuration to a CDC file.
    - Load a saved configuration from a CDC file.
    - Restore the render callback after reloading the screen configuration.
    - Keep unrelated resources in the shared Tutorial.cdc archive.

    Controls:
    - C: Open the configuration screen.
    - S: Save the current configuration.
    - L: Load the saved configuration.
    - Grave: Toggle console.
    - Q / ESC: Quit.
------------------------------------------------------------------------ */

#include "CRM64Pro.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_CONFIG_CDC_NAME "config.xml"
#define OUTPUT_CDC OUTPUTDIR"Tutorial.cdc"

// Render as often as possible while Main::update() keeps a 20 Hz logic rate.
static const Sint32 iLogicRate = 20;
static const Sint32 iRenderRate = 0;

// Runtime resources used by the render callback.
struct TutorialState
{
    Sint32 idBgImage = -1;
    Sint32 idPlayerImage = -1;
};

// Record a successful tutorial task.
static void logTaskOk(Log& mLog)
{
    mLog.msg(LL_DEBUG, "OK\n");
}

// Record a failed tutorial task with its CRM64Pro error code.
static void logTaskFailed(Log& mLog, Sint32 iCode)
{
    mLog.msg(LL_ERROR, "FAILED (Code %d)\n", iCode);
}

// Record a failed tutorial task without an error code.
static void logTaskFailed(Log& mLog)
{
    mLog.msg(LL_ERROR, "FAILED\n");
}

// Draw the background and ship using the current screen dimensions.
static void renderFrame(const TutorialState& rState)
{
    Main& mC64 = Main::instance();
    Screen* pScreen = mC64.configMgr().get();
    Image* pBgImage = mC64.imageMgr().get(rState.idBgImage);
    Image* pPlayerImage = mC64.imageMgr().get(rState.idPlayerImage);
    if(!pScreen) return;

    if(pBgImage)
    {
        SDL_FRect rBg = {
            0.0f,
            0.0f,
            static_cast<float>(pScreen->getWidth()),
            static_cast<float>(pScreen->getHeight())
        };
        pBgImage->render(0, nullptr, &rBg);
    }

    if(pPlayerImage)
    {
        SDL_FRect rPlayer = {
            static_cast<float>(pScreen->getWidth() - pPlayerImage->getWidth()) * 0.5f,
            static_cast<float>(pScreen->getHeight() - pPlayerImage->getHeight()) * 0.5f,
            static_cast<float>(pPlayerImage->getWidth()),
            static_cast<float>(pPlayerImage->getHeight())
        };
        pPlayerImage->render(0, nullptr, &rPlayer);
    }
}

// Screen render callback called by Main::update().
static Sint32 renderWrapper(Sint32, void* pObj)
{
    TutorialState* pState = static_cast<TutorialState*>(pObj);
    if(pState) renderFrame(*pState);
    return 0;
}

// Load config.xml and restore state tied to the recreated default screen.
static bool loadConfiguration(Main& mC64, Log& mLog, TutorialState& rState)
{
    mLog.msg(LL_INFO, "  Load configuration from CDC ... ");
    const Sint32 iRet = mC64.configMgr().load(OUTPUT_CDC, RESOURCE_CONFIG_CDC_NAME);
    if(iRet < 0)
    {
        logTaskFailed(mLog, iRet);
        return false;
    }

    Screen* pScreen = mC64.configMgr().get();
    if(!pScreen)
    {
        logTaskFailed(mLog);
        return false;
    }

    pScreen->setTitle("CRM64Pro - Tutorial 03: Configuration");
    if(!pScreen->setRenderCallback([&rState](Sint32 iMode) { return renderWrapper(iMode, &rState); }))
    {
        logTaskFailed(mLog);
        return false;
    }

    logTaskOk(mLog);
    return true;
}

// Run the tutorial application and release its resources.
int main()
{
    // Start CRM64Pro memory tracking before creating engine resources.
    CMem::setStatsLevel(CMem::MSL_NORMAL);
    atexit(CMem::destroy);

    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_03_Config", LL_DEBUG, LM_FILE | LM_STDOUT, OUTPUTDIR"Tutorial_03_Config.log");

    mLog.msg(LL_INFO, "\nSETUP: timing and Tutorial.cdc\n");

    mLog.msg(LL_INFO, "  Initialize timer ... ");
    if(!mC64.timer().init() || !mC64.timer().setRate(iRenderRate, iLogicRate))
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }
    mC64.configMgr().setMTFriendly(1);
    logTaskOk(mLog);

    // Tutorial 02 creates the shared archive and its image resources.
    mLog.msg(LL_INFO, "  Find Tutorial 02 CDC: %s ... ", OUTPUT_CDC);
    if(!mC64.tool().fileExists(OUTPUT_CDC))
    {
        logTaskFailed(mLog);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    // The built-in setup screen saves config.xml into Tutorial.cdc.
    mLog.msg(LL_INFO, "  Open configuration screen ... ");
    const Sint32 iSetupRet = mC64.configMgr().setup(RESOURCE_CONFIG_CDC_NAME, OUTPUT_CDC);
    if(iSetupRet < 0)
    {
        logTaskFailed(mLog, iSetupRet);
        Main::terminate();
        return -1;
    }
    logTaskOk(mLog);

    if(!loadConfiguration(mC64, mLog, state))
    {
        Main::terminate();
        return -1;
    }

    mLog.msg(LL_INFO, "\nRESOURCES: load Tutorial 02 images\n");

    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);

    Console* pConsole = mC64.guiMgr().getConsole();
    if(pConsole) pConsole->print("Tutorial 03: Configuration ready. Press ` to toggle the console.\n");

    mLog.msg(LL_INFO, "\nCONTROLS\n");
    mLog.msg(LL_INFO, "  [C] Open configuration screen\n");
    mLog.msg(LL_INFO, "  [S] Save current configuration\n");
    mLog.msg(LL_INFO, "  [L] Load saved configuration\n");
    mLog.msg(LL_INFO, "  [Q] / [ESC] Quit\n");
    mLog.msg(LL_INFO, "  [`] Toggle console\n");
    mLog.msg(LL_INFO, "\nRUNNING\n");

    SDL_Event event;
    bool bRunning = true;
    bool bOpenSetup = false;
    bool bSaveConfig = false;
    bool bLoadConfig = false;

    while(bRunning)
    {
        while(mC64.update(&event))
        {
            if(event.type == SDL_EVENT_QUIT)
            {
                bRunning = false;
            }
            else if(event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat)
            {
                if(event.key.key == SDLK_Q || event.key.key == SDLK_ESCAPE) bRunning = false;
                else if(event.key.key == SDLK_C) bOpenSetup = true;
                else if(event.key.key == SDLK_S) bSaveConfig = true;
                else if(event.key.key == SDLK_L) bLoadConfig = true;
            }
        }

        if(!bRunning) break;

        if(bOpenSetup)
        {
            bOpenSetup = false;
            mLog.msg(LL_INFO, "  Open configuration screen ... ");
            const Sint32 iRet = mC64.configMgr().setup(RESOURCE_CONFIG_CDC_NAME, OUTPUT_CDC);
            if(iRet < 0) logTaskFailed(mLog, iRet);
            else
            {
                logTaskOk(mLog);
                if(!loadConfiguration(mC64, mLog, state)) bRunning = false;
            }
        }

        if(bSaveConfig)
        {
            bSaveConfig = false;
            mLog.msg(LL_INFO, "  Save configuration to CDC ... ");
            const Sint32 iRet = mC64.configMgr().save(RESOURCE_CONFIG_CDC_NAME, OUTPUT_CDC);
            if(iRet < 0) logTaskFailed(mLog, iRet);
            else logTaskOk(mLog);
        }

        if(bLoadConfig)
        {
            bLoadConfig = false;
            if(!loadConfiguration(mC64, mLog, state)) bRunning = false;
        }
    }

    mLog.msg(LL_INFO, "\nCLEANUP\n");
    mLog.msg(LL_INFO, "  Close images and CDC archives ... ");
    mC64.imageMgr().close(0);
    mC64.archiveMgr().close(0);
    logTaskOk(mLog);
    Main::terminate();

    return 0;
}
