package wtf.beatrice.retrorender;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Screen;
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 wtf.beatrice.retrorender.engine.DebugHud;
import wtf.beatrice.retrorender.engine.FpsCameraController;
import wtf.beatrice.retrorender.engine.World3D;

public class GameScreen implements Screen {

    private final Main game;

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

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

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

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

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

    private boolean showHud = false;

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

    private void initCamera() {
        camera = new PerspectiveCamera(
            67f,
            RETRO_WIDTH,
            RETRO_HEIGHT
        );

        // near/far + initial direction handled in controller constructor
        cameraController = new FpsCameraController(camera);
    }

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

        // ambient color
        environment.set(
            new ColorAttribute(ColorAttribute.AmbientLight,
                0.4f, 0.4f, 0.5f, 1f)
        );

        // shadow-casting directional light
        shadowLight = new DirectionalShadowLight(
            1024, 1024, // shadow map resolution
            60f, 60f, // viewport size
            1f, 50f // near/far for the light camera
        );
        shadowLight.set(
            1.0f, 0.85f, 0.9f, // casting light color
            -0.7f, -1.0f, -0.3f // direction
        );

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

        shadowBatch = new ModelBatch(new DepthShaderProvider());
    }

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

    private void initHud() {
        hud = new DebugHud();
    }

    // --- screen methods
    private void initRetroBuffer() {
        // RGB + depth
        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);
        // FBO textures are Y-flipped in libGDX, so flip once here
        frameRegion.flip(false, true);

        screenBatch = new com.badlogic.gdx.graphics.g2d.SpriteBatch();
    }

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

        cameraController.onShow();
    }

    @Override
    public void render(float delta) {

        cameraController.update(delta, world);
        world.update(delta);

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

        // --- shadow pass: render depth from light's point of view
        // point to center the shadow camera on;
        // can be adjusted this later (e.g. follow player).
        shadowLight.begin(new com.badlogic.gdx.math.Vector3(0f, 0f, 0f), camera.direction);

        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);
        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);  // DefaultShader uses shadowLight + env.shadowMap
        modelBatch.end();

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

        frameBuffer.end();

        // -- scale framebuffer to screen, preserve aspect ratio --
        int windowW = Gdx.graphics.getWidth(); // logical size
        int windowH = Gdx.graphics.getHeight();

        int backW = Gdx.graphics.getBackBufferWidth(); // actual GL framebuffer
        int backH = Gdx.graphics.getBackBufferHeight();

        // compute how much we can scale the FBO into the window
        float scale = Math.min(
            windowW / (float) RETRO_WIDTH,
            windowH / (float) RETRO_HEIGHT
        );

        // strict integer scaling
        // scale = (float)Math.max(1, Math.floor(scale));

        int drawW = Math.round(RETRO_WIDTH * scale);
        int drawH = Math.round(RETRO_HEIGHT * scale);

        // center the image
        int offsetX = (windowW - drawW) / 2;
        int offsetY = (windowH - drawH) / 2;

        // viewport in backbuffer coordinates
        Gdx.gl.glViewport(0, 0, backW, backH);
        Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        // projection in window (logical) coordinates
        screenBatch.getProjectionMatrix().setToOrtho2D(0, 0, windowW, windowH);

        screenBatch.begin();
        screenBatch.draw(frameRegion, offsetX, offsetY, drawW, 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();
    }
}
