/* ----------------------------------------------------------------------
    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 16 Scene Lighting
------------------------------------------------------------------------

    Overview:
    This tutorial loads a TMX Scene with animated lights, occluder shadows and
    fire particles. The map owns visual properties; this code configures the
    viewport, initial render state and the two independent Scene switches.

    Key concepts:
    - Configure ambient light in the TMX map.
    - Use C64Light objects for candle, pulse and spotlight lighting.
    - Use C64Occluder geometry to cast shadows.
    - Use C64Particle objects with the FIRE preset.
    - Enable and toggle lighting and particle effects independently.

    Layer layout:
    - Layer 1: Image background.
    - Layer 2: C64Light, C64Occluder and C64Particle objects.

    Controls:
    - L: Toggle Scene lighting.
    - E: Toggle Scene particle effects.
    - D: Toggle light, occluder and particle debug shapes.
    - Cursor Keys: Scroll the Scene.
    - Q / ESC: Quit.
------------------------------------------------------------------------ */

#include "CRM64Pro.h"
#include "RunParameter.h"

using namespace CRM64Pro;

#define BASEDIR "Base/"
#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 OUTPUT_CDC OUTPUTDIR"Tutorial.cdc"

static const Sint32 iScreenW = 960;
static const Sint32 iScreenH = 540;
static const Sint32 iViewW = 640;
static const Sint32 iViewH = 480;
static const Sint32 iViewX = (iScreenW - iViewW) / 2;
static const Sint32 iViewY = (iScreenH - iViewH) / 2;
static const Sint32 iLogicRate = 20;
static const float fLogicStep = 1.0f / static_cast<float>(iLogicRate);
static const float fScrollSpeed = 320.0f;

// Store the resources used by the render callback.
struct TutorialState
{
    Scene* pScene = nullptr;
    Sint32 idBgImage = -1;
    Sint32 idFont = -1;
    bool bDebugShapes = true;
};

static TutorialState* g_pState = nullptr;

// Draw the Scene and its screen-space controls.
static Sint32 renderWrapper(Sint32)
{
    if(!g_pState || !g_pState->pScene) return 0;

    Main& mC64 = Main::instance();
    Image* pBackground = mC64.imageMgr().get(g_pState->idBgImage);
    Font* pFont = mC64.fontMgr().get(g_pState->idFont);
    if(pBackground) pBackground->render();
    g_pState->pScene->render();

    if(pFont)
    {
        pFont->setPosition(static_cast<float>(iViewX), 516.0f);
        pFont->render("Cursors scroll   L lighting   E fire effects   D debug   Q/Esc quit");
    }
    return 0;
}

// Load the TMX scene and enable the layers used by the tutorial.
static bool loadScene()
{
    if(!g_pState) return false;

    Main& mC64 = Main::instance();
    const Sint32 idScene = mC64.sceneMgr().loadFromFile(BASEDIR "lighting.tmx");
    g_pState->pScene = mC64.sceneMgr().get(idScene);
    if(!g_pState->pScene) return false;

    SDL_Rect rViewport = { iViewX, iViewY, iViewW, iViewH };
    if(!g_pState->pScene->setViewport(&rViewport)
        || !g_pState->pScene->setLayerFeatures(1, SLF_RENDER, true)
        || !g_pState->pScene->setLayerFeatures(2, SLF_UPDATE | SLF_RENDER, true)
        || !g_pState->pScene->setLayerFeatures(1, SLF_SMOOTHSCROLL, true)
        || !g_pState->pScene->setLayerFeatures(2, SLF_SMOOTHSCROLL, true)) return false;

    g_pState->pScene->setLightingEnabled(true);
    g_pState->pScene->setEffectsEnabled(true);
    return g_pState->pScene->setDebugObjectOverlay(SDO_OBJECT_SHAPE,
        SDOT_LIGHT | SDOT_OCCLUDER | SDOT_PARTICLE, g_pState->bDebugShapes);
}

// Release tutorial-owned resources after complete or partial setup.
static void closeTutorial()
{
    Main& mC64 = Main::instance();
    mC64.sceneMgr().close(0);
    mC64.fontMgr().close(0);
    mC64.imageMgr().close(0);
    Main::terminate();
}

// Run the Scene lighting tutorial.
int main(int argc, char* argv[])
{
    eConfigRendererDriver eRenderer = CRD_SOFTWARE;
    if(!runParameterReadRender(argc, argv, eRenderer)) return 1;

    CMem::setStatsLevel(CMem::MSL_HIGH);
    atexit(CMem::destroy);

    Main& mC64 = Main::instance();
    TutorialState State;
    g_pState = &State;
    Log* pLog = mC64.logMgr().get();

    pLog->init("Tutorial_16_Scene_Lighting", LL_DEBUG, LM_FILE | LM_STDOUT,
        OUTPUTDIR"Tutorial_16_Scene_Lighting.log");
    if(!mC64.timer().init() || !mC64.timer().setRate(0, iLogicRate))
    {
        Main::terminate();
        return -1;
    }
    mC64.configMgr().setMTFriendly(1);

    Screen* pScreen = mC64.configMgr().get();
    pScreen->setSize(iScreenW, iScreenH);
    pScreen->setDriver(eRenderer);
    pScreen->setTitle("Tutorial 16 Scene - Lighting");
    if(pScreen->show() < 0 || !pScreen->setRenderCallback(renderWrapper))
    {
        Main::terminate();
        return -1;
    }

    State.idBgImage = mC64.imageMgr().load(OUTPUT_CDC, RESOURCE_BG_CDC_NAME);
    State.idFont = mC64.fontMgr().getBuiltin("CourierNew10White");
    if(State.idBgImage <= 0 || State.idFont <= 0 || !mC64.fontMgr().get(State.idFont) || !loadScene())
    {
        pLog->msg(LL_ERROR, "Failed to load the lighting tutorial resources or Scene.\n");
        closeTutorial();
        return -1;
    }

    pLog->msg(LL_INFO, "Lighting tutorial loaded. Cursors: scroll, L: lighting, E: fire effects, D: debug shapes.\n");
    SDL_Event Event;
    bool bDone = false;
    while(!bDone)
    {
        while(mC64.update(&Event))
        {
            if(Event.type == SDL_EVENT_QUIT) bDone = true;
            if(Event.type != SDL_EVENT_KEY_DOWN) continue;

            if(Event.key.key == SDLK_ESCAPE || Event.key.key == SDLK_Q) bDone = true;
            else if(Event.key.key == SDLK_L)
            {
                const bool bEnabled = !State.pScene->isLightingEnabled();
                State.pScene->setLightingEnabled(bEnabled);
                pLog->msg(LL_INFO, "Lighting: %s\n", bEnabled ? "ON" : "OFF");
            }
            else if(Event.key.key == SDLK_E)
            {
                const bool bEnabled = !State.pScene->isEffectsEnabled();
                State.pScene->setEffectsEnabled(bEnabled);
                pLog->msg(LL_INFO, "Fire effects: %s\n", bEnabled ? "ON" : "OFF");
            }
            else if(Event.key.key == SDLK_D)
            {
                State.bDebugShapes = !State.bDebugShapes;
                State.pScene->setDebugObjectOverlay(SDO_OBJECT_SHAPE,
                    SDOT_LIGHT | SDOT_OCCLUDER | SDOT_PARTICLE, State.bDebugShapes);
            }
        }
        if(!bDone)
        {
            // Scroll layer 1; linked layers follow its default parallax ratio.
            const float fStep = fScrollSpeed * fLogicStep;
            float fDX = 0.0f;
            float fDY = 0.0f;
            if(mC64.getKeyState(SDLK_LEFT)) fDX -= fStep;
            if(mC64.getKeyState(SDLK_RIGHT)) fDX += fStep;
            if(mC64.getKeyState(SDLK_UP)) fDY -= fStep;
            if(mC64.getKeyState(SDLK_DOWN)) fDY += fStep;
            if(fDX != 0.0f || fDY != 0.0f)
            {
                float fX = 0.0f;
                float fY = 0.0f;
                if(State.pScene->getLayerPosition(1, &fX, &fY) >= 0)
                    State.pScene->setLayerPosition(1, Position(fX + fDX), Position(fY + fDY));
            }
            State.pScene->update();
        }
    }

    closeTutorial();
    return 0;
}
