package de.blight.game.scene;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.light.*;
import com.jme3.material.Material;
import com.jme3.math.*;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.*;
import com.jme3.scene.shape.*;
import com.jme3.shadow.*;
import com.jme3.terrain.geomipmap.*;
import com.jme3.texture.*;
import com.jme3.util.SkyFactory;
import de.blight.game.config.KeyBindings;
import de.blight.game.control.PlayerInputControl;
import de.blight.game.control.ThirdPersonCamera;

public class WorldScene extends BaseAppState {

    private SimpleApplication app;
    private Node              rootNode;
    private AssetManager      assetManager;
    private BulletAppState    bulletAppState;

    private final KeyBindings  keyBindings;
    private ThirdPersonCamera  thirdPersonCam;
    private PlayerInputControl playerInput;

    public WorldScene(KeyBindings keyBindings) {
        this.keyBindings = keyBindings;
    }

    /** Wird von ConfigScreen nach dem Speichern aufgerufen. */
    public void reloadBindings(KeyBindings kb) {
        if (playerInput != null) playerInput.reloadBindings(kb);
    }

    public void setPaused(boolean paused) {
        if (playerInput    != null) playerInput.setPaused(paused);
        if (thirdPersonCam != null) thirdPersonCam.setPaused(paused);
    }

    // -----------------------------------------------------------------------
    // Lifecycle
    // -----------------------------------------------------------------------

    @Override
    protected void initialize(Application app) {
        this.app          = (SimpleApplication) app;
        this.rootNode     = this.app.getRootNode();
        this.assetManager = app.getAssetManager();

        bulletAppState = new BulletAppState();
        app.getStateManager().attach(bulletAppState);
    }

    @Override
    protected void onEnable() {
        buildLighting();
        TerrainQuad terrain = buildTerrain();
        buildDecorations(terrain);

        Node character = buildCharacter();
        rootNode.attachChild(character);

        // Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
        CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
        CharacterControl physicsChar  = new CharacterControl(capsule, 0.05f);
        physicsChar.setJumpSpeed(12f);
        physicsChar.setFallSpeed(35f);
        physicsChar.setGravity(35f);
        physicsChar.setPhysicsLocation(new Vector3f(0, 5f, 0));
        character.addControl(physicsChar);
        bulletAppState.getPhysicsSpace().add(physicsChar);

        playerInput = new PlayerInputControl(app.getInputManager(), app.getCamera(), keyBindings);
        playerInput.setPhysicsCharacter(physicsChar);
        playerInput.setVisual(character);

        thirdPersonCam = new ThirdPersonCamera(app.getCamera(), app.getInputManager());
        thirdPersonCam.setTarget(character);

        // Maus einfangen – keine Klick-Pflicht für Kamerasteuerung
        app.getInputManager().setCursorVisible(false);
    }

    @Override
    public void update(float tpf) {
        playerInput.update(tpf);
        thirdPersonCam.update(tpf);
    }

    @Override protected void cleanup(Application app) {}
    @Override protected void onDisable() {}

    // -----------------------------------------------------------------------
    // Beleuchtung
    // -----------------------------------------------------------------------

    private void buildLighting() {
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal());
        sun.setColor(ColorRGBA.White.mult(1.4f));
        rootNode.addLight(sun);

        AmbientLight ambient = new AmbientLight();
        ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.4f, 1f));
        rootNode.addLight(ambient);

        DirectionalLightShadowRenderer shadowRenderer =
            new DirectionalLightShadowRenderer(assetManager, 2048, 3);
        shadowRenderer.setLight(sun);
        shadowRenderer.setShadowIntensity(0.4f);
        app.getViewPort().addProcessor(shadowRenderer);

        try {
            Spatial sky = SkyFactory.createSky(assetManager,
                "Textures/Sky/Bright/BrightSky.dds",
                SkyFactory.EnvMapType.CubeMap);
            rootNode.attachChild(sky);
        } catch (Exception ignored) {}
    }

    // -----------------------------------------------------------------------
    // Terrain
    // -----------------------------------------------------------------------

    private TerrainQuad buildTerrain() {
        int size = 257;
        float[] heights = new float[size * size];

        for (int z = 0; z < size; z++) {
            for (int x = 0; x < size; x++) {
                float nx = x / (float) size;
                float nz = z / (float) size;
                heights[z * size + x] =
                    FastMath.sin(nx * FastMath.TWO_PI * 2) * 2f
                  + FastMath.sin(nz * FastMath.TWO_PI * 3) * 1.5f
                  + FastMath.sin((nx + nz) * FastMath.TWO_PI * 1.5f) * 1f;
            }
        }

        TerrainQuad terrain = new TerrainQuad("terrain", 65, size, heights);

        Material terrainMat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
        Texture grass = assetManager.loadTexture("Textures/gras.png");
        grass.setWrap(Texture.WrapMode.Repeat);
        terrainMat.setTexture("Tex1", grass);
        terrainMat.setFloat("Tex1Scale", 64f);

        terrain.setMaterial(terrainMat);
        terrain.setLocalTranslation(0, -5f, 0);
        terrain.setLocalScale(0.5f, 0.5f, 0.5f);
        terrain.setShadowMode(RenderQueue.ShadowMode.Receive);

        // Statischer Physics-Body (mass=0) für Terrain-Kollision
        RigidBodyControl terrainPhysics = new RigidBodyControl(
            CollisionShapeFactory.createMeshShape(terrain), 0f);
        terrain.addControl(terrainPhysics);
        bulletAppState.getPhysicsSpace().add(terrainPhysics);

        rootNode.attachChild(terrain);
        return terrain;
    }

    // -----------------------------------------------------------------------
    // Dekorationen – Höhe per TerrainQuad.getHeight() anpassen
    // -----------------------------------------------------------------------

    private void buildDecorations(TerrainQuad terrain) {
        Material stoneMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        stoneMat.setBoolean("UseMaterialColors", true);
        stoneMat.setColor("Diffuse",  new ColorRGBA(0.55f, 0.55f, 0.55f, 1f));
        stoneMat.setColor("Ambient",  new ColorRGBA(0.3f,  0.3f,  0.3f,  1f));
        stoneMat.setColor("Specular", ColorRGBA.White);
        stoneMat.setFloat("Shininess", 32f);

        Material treeTrunkMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        treeTrunkMat.setBoolean("UseMaterialColors", true);
        treeTrunkMat.setColor("Diffuse", new ColorRGBA(0.45f, 0.28f, 0.1f, 1f));
        treeTrunkMat.setColor("Ambient", new ColorRGBA(0.2f,  0.12f, 0.04f, 1f));
        treeTrunkMat.setColor("Specular", ColorRGBA.Black);

        Material treeTopMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        treeTopMat.setBoolean("UseMaterialColors", true);
        treeTopMat.setColor("Diffuse", new ColorRGBA(0.1f, 0.55f, 0.15f, 1f));
        treeTopMat.setColor("Ambient", new ColorRGBA(0.05f, 0.25f, 0.07f, 1f));
        treeTopMat.setColor("Specular", ColorRGBA.Black);

        float[][] treeXZ = {
            {12, 8}, {-15, 5}, {20, -10}, {-8, -18},
            {5, 25}, {-22, 12}, {18, 20}, {-10, -5}
        };
        for (float[] xz : treeXZ) {
            float worldY = terrainWorldY(terrain, xz[0], xz[1]);
            Node tree = new Node("tree");

            Geometry trunk = new Geometry("trunk", new Cylinder(8, 8, 0.25f, 2.5f, true));
            trunk.setMaterial(treeTrunkMat);
            trunk.rotate(FastMath.HALF_PI, 0, 0);
            trunk.setLocalTranslation(0, 1.25f, 0);
            trunk.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

            Geometry crown = new Geometry("crown", new Sphere(12, 12, 2.2f));
            crown.setMaterial(treeTopMat);
            crown.setLocalTranslation(0, 3.8f, 0);
            crown.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

            tree.attachChild(trunk);
            tree.attachChild(crown);
            tree.setLocalTranslation(xz[0], worldY, xz[1]);
            rootNode.attachChild(tree);
        }

        float[][] stoneXZ = {{6, -6}, {-12, 15}, {16, -4}, {-3, 10}};
        for (float[] xz : stoneXZ) {
            float worldY = terrainWorldY(terrain, xz[0], xz[1]);
            float r = 0.6f + FastMath.nextRandomFloat() * 0.8f;
            Geometry stone = new Geometry("stone", new Sphere(8, 8, r));
            stone.setMaterial(stoneMat);
            stone.setLocalTranslation(xz[0], worldY + r * 0.5f, xz[1]);
            stone.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
            rootNode.attachChild(stone);
        }
    }

    /** Konvertiert Welt-XZ in lokale Terrain-Koordinaten und fragt die Höhe ab. */
    private float terrainWorldY(TerrainQuad terrain, float worldX, float worldZ) {
        Vector3f terrainTranslation = terrain.getWorldTranslation();
        Vector3f terrainScale       = terrain.getWorldScale();
        float localX = (worldX - terrainTranslation.x) / terrainScale.x;
        float localZ = (worldZ - terrainTranslation.z) / terrainScale.z;
        float localH = terrain.getHeight(new Vector2f(localX, localZ));
        return terrainTranslation.y + localH * terrainScale.y;
    }

    // -----------------------------------------------------------------------
    // Charakter (visueller Node, ohne eigenes Mesh für Physics)
    // -----------------------------------------------------------------------

    private Node buildCharacter() {
        Node character = new Node("character");

        Material bodyMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        bodyMat.setBoolean("UseMaterialColors", true);
        bodyMat.setColor("Diffuse",  new ColorRGBA(0.2f, 0.4f, 0.8f, 1f));
        bodyMat.setColor("Ambient",  new ColorRGBA(0.1f, 0.2f, 0.4f, 1f));
        bodyMat.setColor("Specular", ColorRGBA.White);
        bodyMat.setFloat("Shininess", 64f);

        Material headMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        headMat.setBoolean("UseMaterialColors", true);
        headMat.setColor("Diffuse",  new ColorRGBA(0.9f, 0.75f, 0.6f, 1f));
        headMat.setColor("Ambient",  new ColorRGBA(0.45f, 0.37f, 0.3f, 1f));
        headMat.setColor("Specular", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f));
        headMat.setFloat("Shininess", 16f);

        Geometry body = new Geometry("body", new Cylinder(8, 16, 0.35f, 1.1f, true));
        body.setMaterial(bodyMat);
        body.rotate(FastMath.HALF_PI, 0, 0);
        body.setLocalTranslation(0, 0.9f, 0);
        body.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

        Geometry head = new Geometry("head", new Sphere(16, 16, 0.28f));
        head.setMaterial(headMat);
        head.setLocalTranslation(0, 1.75f, 0);
        head.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);

        Geometry armL = buildLimb(bodyMat, 0.1f, 0.55f);
        armL.setLocalTranslation(-0.5f, 0.85f, 0);
        armL.rotate(0, 0, FastMath.DEG_TO_RAD * 20f);

        Geometry armR = buildLimb(bodyMat, 0.1f, 0.55f);
        armR.setLocalTranslation(0.5f, 0.85f, 0);
        armR.rotate(0, 0, FastMath.DEG_TO_RAD * -20f);

        Geometry legL = buildLimb(bodyMat, 0.12f, 0.6f);
        legL.setLocalTranslation(-0.18f, 0.3f, 0);

        Geometry legR = buildLimb(bodyMat, 0.12f, 0.6f);
        legR.setLocalTranslation(0.18f, 0.3f, 0);

        character.attachChild(body);
        character.attachChild(head);
        character.attachChild(armL);
        character.attachChild(armR);
        character.attachChild(legL);
        character.attachChild(legR);

        return character;
    }

    private Geometry buildLimb(Material mat, float radius, float height) {
        Geometry limb = new Geometry("limb", new Cylinder(6, 12, radius, height, true));
        limb.setMaterial(mat);
        limb.rotate(FastMath.HALF_PI, 0, 0);
        limb.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
        return limb;
    }
}
