package wtf.beatrice.retrorender.engine.render;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalShadowLight;
import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.math.Vector3;
import wtf.beatrice.retrorender.Main;
import wtf.beatrice.retrorender.engine.config.GameSettings;
import wtf.beatrice.retrorender.engine.world.ModelLibrary;
import wtf.beatrice.retrorender.engine.ui.DebugHud;
import wtf.beatrice.retrorender.engine.ui.GameUi;
import wtf.beatrice.retrorender.engine.ui.UiMode;
import wtf.beatrice.retrorender.engine.world.World3D;

public class GameScreen implements Screen {

    private final Main game;

    private PerspectiveCamera camera;
    private ModelBatch modelBatch;
    private Environment environment;

    private ColorAttribute ambientLight;
    private DayNightCycle dayNightCycle;

    private FpsCameraController cameraController;
    private World3D world;
    private DebugHud hud;

    private boolean showHud = false;

    // menus
    private GameSettings settings;
    private GameUi gameUi;
    private UiMode uiMode = UiMode.GAMEPLAY;

    // Shadow Mapping
    private ModelBatch shadowBatch;
    private DirectionalShadowLight shadowLight;

    // Retro rendering
    private FrameBuffer frameBuffer;
    private TextureRegion frameRegion;
    private SpriteBatch screenBatch;

    // world
    private ModelLibrary modelLibrary;


    private static final int RETRO_WIDTH = 430;
    private static final int RETRO_HEIGHT = 240;

    public GameScreen(Main game) {
        this.game = game;
    }

    private void initCamera() {
        settings = new GameSettings();
        camera = new PerspectiveCamera(settings.fov, RETRO_WIDTH, RETRO_HEIGHT);
        cameraController = new FpsCameraController(camera, settings);
    }

    private void initEnvironment() {
        environment = new Environment();

        // keep a handle to ambient so we can animate it
        ambientLight = new ColorAttribute(
            ColorAttribute.AmbientLight,
            0.4f, 0.4f, 0.5f, 1f
        );
        environment.set(ambientLight);

        // shadow-casting directional light (our sun)
        shadowLight = new DirectionalShadowLight(
            1024, 1024, // shadow map resolution
            60f, 60f,   // viewport size
            1f, 100f     // near/far for the light camera
        );

        environment.add(shadowLight);
        environment.shadowMap = shadowLight;

        shadowBatch = new ModelBatch(new DepthShaderProvider());

        // create the cycle controller
        dayNightCycle = new DayNightCycle(shadowLight, ambientLight);
        // optional: start at morning/noon/etc
        dayNightCycle.setTimeOfDay(0.2f); // good sun
        dayNightCycle.setAxialTilt(30f);
        dayNightCycle.setDayLength(10f); // 5 minutes
    }

    private void initWorld() {
        modelLibrary = new ModelLibrary();
        world = new World3D(modelLibrary);
    }

    private void initMenus() {
        hud = new DebugHud(dayNightCycle);
        gameUi = new GameUi(settings);
    }

    private void initRetroBuffer() {
        frameBuffer = new FrameBuffer(
            com.badlogic.gdx.graphics.Pixmap.Format.RGBA8888,
            RETRO_WIDTH,
            RETRO_HEIGHT,
            true
        );

        Texture fbTex = frameBuffer.getColorBufferTexture();
        frameRegion = new TextureRegion(fbTex);
        fbTex.setFilter(Texture.TextureFilter.Nearest, Texture.TextureFilter.Nearest);
        frameRegion.flip(false, true);

        screenBatch = new SpriteBatch();
    }

    @Override
    public void show() {
        modelBatch = new ModelBatch();
        initCamera();
        initEnvironment();
        initWorld();
        initMenus();
        initRetroBuffer();

        cameraController.onShow(); // captures mouse with warmup
    }

    @Override
    public void render(float delta) {

        // TAB: toggle hud
        if (Gdx.input.isKeyJustPressed(Input.Keys.TAB)) {
            showHud = !showHud;
        }

        // ESC: toggle pause and mouse capture
        if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
            UiMode newMode = gameUi.onEsc(uiMode);
            boolean goingToGameplay = (newMode == UiMode.GAMEPLAY);
            uiMode = newMode;

            if (uiMode == UiMode.GAMEPLAY) {
                cameraController.captureMouse();
            } else {
                cameraController.releaseMouse();
            }
        }

        boolean gameplay = (uiMode == UiMode.GAMEPLAY);

        if (gameplay) {
            cameraController.update(delta, world);
            world.update(delta);
        }

        // update day/night (you can choose to run even when paused if you prefer)
        if (dayNightCycle != null && gameplay) {
            dayNightCycle.update(delta);
        }

        // --- shadow pass ---
        boolean doShadows = (dayNightCycle == null) || dayNightCycle.isSunAboveHorizon();

        if (doShadows) {
            if (dayNightCycle != null) {
                Vector3 center   = new Vector3(camera.position.x, 0f, camera.position.z);
                Vector3 lightDir = dayNightCycle.getSunDirection();

                shadowLight.begin(center, lightDir);
            } else {
                shadowLight.begin(new Vector3(0f, 0f, 0f), new Vector3(-0.7f, -1f, -0.3f));
            }

            shadowBatch.begin(shadowLight.getCamera());
            world.render(shadowBatch, environment);  // depth-only pass
            shadowBatch.end();
            shadowLight.end();
        }

        // --- render scene into low res framebuffer ---
        frameBuffer.begin();
        Gdx.gl.glViewport(0, 0, RETRO_WIDTH, RETRO_HEIGHT);
        if (dayNightCycle != null) {
            Color sky = dayNightCycle.getSkyColor();
            Gdx.gl.glClearColor(sky.r, sky.g, sky.b, 1f);
        } else {
            Gdx.gl.glClearColor(0.5f, 0.6f, 1.0f, 1f);
        }
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

        camera.update();
        modelBatch.begin(camera);
        world.render(modelBatch, environment);
        modelBatch.end();

        // HUD
        if (showHud) {
            hud.render(
                RETRO_WIDTH,
                RETRO_HEIGHT,
                camera,
                cameraController.getYaw(),
                cameraController.getPitch()
            );
        }

        // pause UI
        if (uiMode != UiMode.GAMEPLAY) {
            gameUi.render(RETRO_WIDTH, RETRO_HEIGHT, uiMode);
        }

        frameBuffer.end();

        // -- scale framebuffer to screen, preserve aspect ratio --
        int windowW = Gdx.graphics.getWidth();
        int windowH = Gdx.graphics.getHeight();
        int backW   = Gdx.graphics.getBackBufferWidth();
        int backH   = Gdx.graphics.getBackBufferHeight();

        RetroViewportHelper.RetroViewport vp =
            RetroViewportHelper.computeViewport(windowW, windowH, RETRO_WIDTH, RETRO_HEIGHT);

        // click → UI
        if (uiMode != UiMode.GAMEPLAY && Gdx.input.justTouched()) {
            int mouseX = Gdx.input.getX();
            int mouseY = Gdx.input.getY();

            RetroViewportHelper.RetroClick rc =
                RetroViewportHelper.toRetroCoords(
                    vp,
                    RETRO_WIDTH, RETRO_HEIGHT,
                    mouseX, mouseY,
                    windowH
                );

            if (rc.inside) {
                GameUi.UiResult result = gameUi.handleClick(rc.x, rc.y, uiMode);

                switch (result) {
                    case RESUME:
                        uiMode = UiMode.GAMEPLAY;
                        cameraController.captureMouse();
                        break;

                    case OPEN_SETTINGS:
                        uiMode = UiMode.SETTINGS;
                        // mouse already released when we entered PAUSE, so nothing to do
                        break;

                    case CLOSE_SETTINGS:
                        uiMode = UiMode.PAUSE;
                        // stay in pause, mouse remains free
                        break;

                    case QUIT:
                        Gdx.app.exit();
                        break;

                    case NONE:
                    default:
                        break;
                }
            }
        }

        // viewport in backbuffer coordinates
        // final draw using vp.offsetX / vp.offsetY / vp.drawW / vp.drawH
        Gdx.gl.glViewport(0, 0, backW, backH);
        screenBatch.getProjectionMatrix().setToOrtho2D(0, 0, windowW, windowH);

        screenBatch.begin();
        screenBatch.draw(frameRegion, vp.offsetX, vp.offsetY, vp.drawW, vp.drawH);
        screenBatch.end();
    }

    @Override
    public void resize(int width, int height) {
        camera.viewportWidth = RETRO_WIDTH;
        camera.viewportHeight = RETRO_HEIGHT;
        camera.update();

        cameraController.resize(width, height);
    }

    @Override public void pause() { }
    @Override public void resume() { }

    @Override
    public void hide() {
        cameraController.onHide();
    }

    @Override
    public void dispose() {
        cameraController.dispose();
        world.dispose();
        modelBatch.dispose();
        hud.dispose();
        shadowBatch.dispose();
        shadowLight.dispose();
        if (gameUi != null) gameUi.dispose();
        if (frameBuffer != null) frameBuffer.dispose();
        if (screenBatch != null) screenBatch.dispose();
    }
}
