package de.blight.editor.state;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.BaseAppState;
import com.jme3.asset.AssetManager;
import com.jme3.collision.CollisionResults;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.*;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Quad;
import com.jme3.util.BufferUtils;
import de.blight.editor.SharedInput;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

public class TerrainEditorState extends BaseAppState {

    // ── Konstanten ──────────────────────────────────────────────────────────
    private static final int   V            = 17;       // Vertices pro Achse (16 Zellen)
    private static final float BRUSH_RADIUS = 2.0f;     // Meter
    private static final float BRUSH_DELTA  = 0.25f;    // Höhenänderung pro Klick
    private static final float CAM_SPEED    = 12f;
    private static final float MOUSE_SENS   = 0.003f;

    // ── Zustand ─────────────────────────────────────────────────────────────
    private SimpleApplication app;
    private Camera             cam;
    private AssetManager       assets;
    private Node               rootNode;
    private final SharedInput  input;

    private final float[] heights = new float[V * V]; // flaches Array, Index = z*V+x
    private Mesh           terrainMesh;
    private Geometry       terrainGeo;

    // Kamera-Euler-Winkel
    private float camYaw   = 0f;
    private float camPitch = -0.4f;
    private final Vector3f camPos = new Vector3f(0, 14, 22);

    public TerrainEditorState(SharedInput input) {
        this.input = input;
    }

    // ── Lifecycle ────────────────────────────────────────────────────────────

    @Override
    protected void initialize(Application app) {
        this.app      = (SimpleApplication) app;
        this.cam      = app.getCamera();
        this.assets   = app.getAssetManager();
        this.rootNode = this.app.getRootNode();
    }

    @Override
    protected void onEnable() {
        buildScene();
        applyCameraTransform();
    }

    @Override
    protected void onDisable() {
        rootNode.detachAllChildren();
    }

    @Override protected void cleanup(Application app) {}

    // ── Szene aufbauen ───────────────────────────────────────────────────────

    private void buildScene() {
        // Licht
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal());
        sun.setColor(new ColorRGBA(1.2f, 1.1f, 0.9f, 1f));
        rootNode.addLight(sun);

        AmbientLight ambient = new AmbientLight(new ColorRGBA(0.35f, 0.38f, 0.45f, 1f));
        rootNode.addLight(ambient);

        // Terrain
        terrainGeo = buildTerrainGeometry();
        rootNode.attachChild(terrainGeo);

        // Wasser bei Y = 0
        rootNode.attachChild(buildWater());

        // Raster-Linien auf dem Terrain (als dünne Linien-Node wäre komplex, Gitter via Grid-Overlay)
        // Einfache Grid-Markierung: ein flaches transparentes Quad mit Wireframe
        rootNode.attachChild(buildGridOverlay());

        // Himmel (einfacher Hintergrund-Farbverlauf über Viewport-BG-Farbe)
        app.getViewPort().setBackgroundColor(new ColorRGBA(0.45f, 0.60f, 0.80f, 1f));
    }

    private Geometry buildTerrainGeometry() {
        terrainMesh = new Mesh();
        FloatBuffer posBuf  = BufferUtils.createFloatBuffer(V * V * 3);
        FloatBuffer normBuf = BufferUtils.createFloatBuffer(V * V * 3);
        FloatBuffer texBuf  = BufferUtils.createFloatBuffer(V * V * 2);
        IntBuffer   idxBuf  = BufferUtils.createIntBuffer((V - 1) * (V - 1) * 6);

        for (int z = 0; z < V; z++) {
            for (int x = 0; x < V; x++) {
                posBuf.put(x).put(heights[z * V + x]).put(z);
                normBuf.put(0).put(1).put(0);
                texBuf.put(x / 16f).put(z / 16f);
            }
        }
        for (int z = 0; z < V - 1; z++) {
            for (int x = 0; x < V - 1; x++) {
                int bl = z * V + x, br = bl + 1;
                int tl = bl + V,    tr = tl + 1;
                idxBuf.put(bl).put(tr).put(br);
                idxBuf.put(bl).put(tl).put(tr);
            }
        }

        terrainMesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
        terrainMesh.setBuffer(VertexBuffer.Type.Normal,   3, normBuf);
        terrainMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, texBuf);
        terrainMesh.setBuffer(VertexBuffer.Type.Index,    3, idxBuf);
        terrainMesh.updateBound();

        Geometry geo = new Geometry("terrain", terrainMesh);
        geo.setLocalTranslation(-8, 0, -8); // Terrain zentriert bei Ursprung

        Material mat = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
        mat.setBoolean("UseMaterialColors", true);
        mat.setColor("Diffuse",  new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
        mat.setColor("Ambient",  new ColorRGBA(0.12f, 0.28f, 0.08f, 1f));
        mat.setColor("Specular", ColorRGBA.Black);
        mat.setFloat("Shininess", 0f);
        geo.setMaterial(mat);
        return geo;
    }

    private Geometry buildWater() {
        Geometry water = new Geometry("water", new Quad(16, 16));
        water.rotate(-FastMath.HALF_PI, 0, 0);
        water.setLocalTranslation(-8, 0.01f, 8); // leicht über Y=0 damit kein Z-Fighting

        Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", new ColorRGBA(0.05f, 0.25f, 0.70f, 0.55f));
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        water.setQueueBucket(RenderQueue.Bucket.Transparent);
        water.setMaterial(mat);
        return water;
    }

    private Geometry buildGridOverlay() {
        // Einfaches Wireframe-Duplikat des Terrains als Gitter
        Geometry grid = new Geometry("grid", terrainMesh);
        grid.setLocalTranslation(-8, 0.02f, -8);

        Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", new ColorRGBA(0f, 0f, 0f, 0.25f));
        mat.getAdditionalRenderState().setWireframe(true);
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        grid.setQueueBucket(RenderQueue.Bucket.Transparent);
        grid.setMaterial(mat);
        return grid;
    }

    // ── Update-Schleife ──────────────────────────────────────────────────────

    @Override
    public void update(float tpf) {
        updateCamera(tpf);
        processEdits();
    }

    private void updateCamera(float tpf) {
        // Rotation aus akkumuliertem Maus-Delta
        int[] delta = input.consumeMouseDelta();
        if (delta[0] != 0 || delta[1] != 0) {
            camYaw   -= delta[0] * MOUSE_SENS;
            camPitch -= delta[1] * MOUSE_SENS;
            camPitch  = FastMath.clamp(camPitch,
                -FastMath.HALF_PI + 0.05f,
                 FastMath.HALF_PI - 0.05f);
        }

        applyCameraTransform();

        // Bewegung in Kamera-Vorwärtsrichtung projiziert auf XZ-Ebene
        float speed = CAM_SPEED * tpf;
        Vector3f fwd  = cam.getDirection().clone().setY(0);
        if (fwd.lengthSquared() > 0.001f) fwd.normalizeLocal();
        Vector3f lft  = cam.getLeft().clone().setY(0);
        if (lft.lengthSquared() > 0.001f) lft.normalizeLocal();

        if (input.forward)  camPos.addLocal(fwd.mult(speed));
        if (input.backward) camPos.subtractLocal(fwd.mult(speed));
        if (input.left)     camPos.addLocal(lft.mult(speed));
        if (input.right)    camPos.subtractLocal(lft.mult(speed));
        if (input.up)       camPos.y += speed;
        if (input.down)     camPos.y -= speed;

        cam.setLocation(camPos);
    }

    private void applyCameraTransform() {
        Quaternion yawQ   = new Quaternion().fromAngleAxis(camYaw,   Vector3f.UNIT_Y);
        Quaternion pitchQ = new Quaternion().fromAngleAxis(camPitch, Vector3f.UNIT_X);
        cam.setRotation(yawQ.mult(pitchQ));
        cam.setLocation(camPos);
    }

    private void processEdits() {
        SharedInput.TerrainEdit edit;
        while ((edit = input.editQueue.poll()) != null) {
            // JavaFX-Koordinaten → JME3-Screen-Koordinaten (Y spiegeln)
            float jmeX = (float)(edit.screenX() * input.viewportScaleX);
            float jmeY = cam.getHeight() - (float)(edit.screenY() * input.viewportScaleY);

            Vector3f near = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 0f);
            Vector3f far  = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 1f);
            Vector3f dir  = far.subtract(near).normalizeLocal();

            CollisionResults hits = new CollisionResults();
            terrainGeo.collideWith(new com.jme3.math.Ray(near, dir), hits);

            if (hits.size() > 0) {
                Vector3f contact = hits.getClosestCollision().getContactPoint();
                modifyHeight(contact, edit.action() * BRUSH_DELTA);
            }
        }
    }

    // ── Höhen-Werkzeug ───────────────────────────────────────────────────────

    private void modifyHeight(Vector3f worldContact, float delta) {
        // Terrain-Geometrie ist bei (-8, 0, -8), Vertices bei (0..16, h, 0..16)
        float localX = worldContact.x + 8;
        float localZ = worldContact.z + 8;

        for (int z = 0; z < V; z++) {
            for (int x = 0; x < V; x++) {
                float dx   = x - localX;
                float dz   = z - localZ;
                float dist = FastMath.sqrt(dx * dx + dz * dz);
                if (dist < BRUSH_RADIUS) {
                    float falloff = 1f - dist / BRUSH_RADIUS;
                    heights[z * V + x] += delta * falloff;
                }
            }
        }
        updateTerrainMesh();
    }

    private void updateTerrainMesh() {
        FloatBuffer posBuf  = terrainMesh.getFloatBuffer(VertexBuffer.Type.Position);
        FloatBuffer normBuf = terrainMesh.getFloatBuffer(VertexBuffer.Type.Normal);

        posBuf.rewind();
        for (int z = 0; z < V; z++) {
            for (int x = 0; x < V; x++) {
                posBuf.put(x).put(heights[z * V + x]).put(z);
            }
        }

        // Normalen per finite differences
        normBuf.rewind();
        for (int z = 0; z < V; z++) {
            for (int x = 0; x < V; x++) {
                float hL = x > 0   ? heights[z * V + (x - 1)] : heights[z * V + x];
                float hR = x < V-1 ? heights[z * V + (x + 1)] : heights[z * V + x];
                float hD = z > 0   ? heights[(z - 1) * V + x] : heights[z * V + x];
                float hU = z < V-1 ? heights[(z + 1) * V + x] : heights[z * V + x];
                float nx = -(hR - hL);
                float ny = 2.0f;
                float nz = -(hU - hD);
                float len = FastMath.sqrt(nx*nx + ny*ny + nz*nz);
                normBuf.put(nx / len).put(ny / len).put(nz / len);
            }
        }

        terrainMesh.getBuffer(VertexBuffer.Type.Position).setUpdateNeeded();
        terrainMesh.getBuffer(VertexBuffer.Type.Normal).setUpdateNeeded();
        terrainMesh.updateBound();
        terrainGeo.updateModelBound();
    }
}
