using LandblockExtraction.DatEngine;
using LandblockExtraction.LandBlockExtractor;
using Map3DRendering.Common;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;

namespace Map3DRendering {
    public class MapRender {
        private PortalEngine portalEngine;
        private CellEngine cellEngine;
        private LandBlockExtrator landblockExtraction;

        private readonly int NumberLandBlocks = 255;
        private readonly int BlockSize = 17;
        private readonly int allBlocks = 255 * 17 * 255 * 17;
        private readonly int cellSize = 8;
        private readonly int radius = 0x10; // Rayon du voisinage

        public int[,] _vertexArrayObject;
        public int[,] _vertexBufferObject;
        public int[,] _elementBufferObject;
        public bool[,] _mapExiste;

        public int currentLandBlockX { get; private set; } // Position X du landblock actuel
        public int currentLandBlockY { get; private set; } // Position Y du landblock actuel
        public int startX { get; private set; }
        public int startY { get; private set; }
        public int endX { get; private set; }
        public int endY { get; private set; }
        public MapRender() {
            portalEngine = new PortalEngine();
            cellEngine = new CellEngine();
            landblockExtraction = new(portalEngine, cellEngine);

            _vertexArrayObject = new int[0xFE, 0xFE];
            _vertexBufferObject = new int[0xFE, 0xFE];
            _elementBufferObject = new int[0xFE, 0xFE];
            _mapExiste = new bool[0xFE, 0xFE];

            currentLandBlockX = 0x7F;
            currentLandBlockY = 0x7F;

            CalculeRadius(currentLandBlockX, currentLandBlockY);
        }

        public int GetIndiceLenght() {
            return (17 - 1) * (17 - 1) * 6; //Always that.
        }
        public void CalculeRadius(int x, int y) {
            currentLandBlockX = x;
            currentLandBlockY = y;
            startX = Math.Max(0, currentLandBlockX - radius);
            startY = Math.Max(0, currentLandBlockY - radius);
            endX = Math.Min(NumberLandBlocks - 1, currentLandBlockX + radius);
            endY = Math.Min(NumberLandBlocks - 1, currentLandBlockY + radius);
        }
        public void OnLoad(Shader _shader) {
            for (int landY = startY; landY <= endY; landY++) {
                for (int landX = startX; landX <= endX; landX++) {
                    if (!_mapExiste[landX, landY]) {
                        var block = landblockExtraction.GetBlock(landX, landY);
                        if (block != null) {
                            InitializeBlock(landX, landY, block, _shader);
                            _mapExiste[landX, landY] = true;
                        }
                    }
                }
            }
        }
        private void InitializeBlock(int x, int y, BlockStruct block, Shader _shader) {
            int lenghPacket = 11;
            // Initialisez le VAO, VBO et EBO pour le bloc à (x, y)...
            // Utilisez le code de votre méthode OnLoad originale pour configurer le VAO, VBO et EBO.
            int tempVertexArray = GL.GenVertexArray();
            GL.BindVertexArray(tempVertexArray);
            _vertexArrayObject[x, y] = tempVertexArray;

            int tmpVertexBuffer = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ArrayBuffer, tmpVertexBuffer);
            GL.BufferData(BufferTarget.ArrayBuffer, block.verticesStruct.Vertices().Length * sizeof(float), block.verticesStruct.Vertices(), BufferUsageHint.StaticDraw);
            _vertexBufferObject[x, y] = tmpVertexBuffer;

            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, lenghPacket * sizeof(float), 0);

            int tmpElementBuffer = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, tmpElementBuffer);
            GL.BufferData(BufferTarget.ElementArrayBuffer, block.indices.Length * sizeof(int), block.indices, BufferUsageHint.StaticDraw);
            _elementBufferObject[x, y] = tmpElementBuffer;

            var vertexLocation = _shader.GetAttribLocation("aPos");
            GL.EnableVertexAttribArray(vertexLocation);
            GL.VertexAttribPointer(vertexLocation, 3, VertexAttribPointerType.Float, false, lenghPacket * sizeof(float), 0);

            var colorLocation = _shader.GetAttribLocation("aColor");
            GL.EnableVertexAttribArray(colorLocation);
            GL.VertexAttribPointer(colorLocation, 4, VertexAttribPointerType.Float, false, lenghPacket * sizeof(float), 3 * sizeof(float));

            var farcolorLocation = _shader.GetAttribLocation("aColorFar");
            GL.EnableVertexAttribArray(farcolorLocation);
            GL.VertexAttribPointer(farcolorLocation, 4, VertexAttribPointerType.Float, false, lenghPacket * sizeof(float), 7 * sizeof(float));
        }
        public void Render(Shader shader) {
            for (int y = startY; y <= endY; y++) {
                for (int x = startX; x <= endX; x++) {
                    if (_mapExiste[x, y]) {
                        var model = Matrix4.Identity;//CreateTranslation(x * BlockSize, 0, y * BlockSize); // Ajustez selon votre système de coordonnées
                        shader.SetMatrix4("model", model);
                        GL.BindVertexArray(_vertexArrayObject[x, y]);
                        GL.DrawElements(PrimitiveType.Lines, GetIndiceLenght(), DrawElementsType.UnsignedInt, 0);
                    }
                }
            }
        }
        public void CleanupBlock(int x, int y) {
            if (_mapExiste[x, y]) {
                GL.DeleteBuffer(_vertexBufferObject[x, y]);
                GL.DeleteBuffer(_elementBufferObject[x, y]);
                GL.DeleteVertexArray(_vertexArrayObject[x, y]);
                _mapExiste[x, y] = false;
            }
        }
        public void UpdateBlocks(Vector3 cameraPosition, Shader shader) {
            // Convertir la position de la caméra en coordonnées de landblock
            int newLandBlockX = ConvertPositionToLandBlockCoord(cameraPosition.X);
            int newLandBlockY = (0xFF) - ConvertPositionToLandBlockCoord(cameraPosition.Z);

            // Vérifier si la caméra a déplacé suffisamment pour nécessiter une mise à jour des blocs
            if (newLandBlockX != currentLandBlockX || newLandBlockY != currentLandBlockY) {
                // Nettoyer les blocs qui ne sont plus dans la nouvelle zone visible
                for (int y = startY; y <= endY; y++) {
                    for (int x = startX; x <= endX; x++) {
                        if (!IsInNewRadius(x, y, newLandBlockX, newLandBlockY)) {
                            CleanupBlock(x, y);
                        }
                    }
                }

                // Mettre à jour les coordonnées du rayon pour la nouvelle position
                CalculeRadius(newLandBlockX, newLandBlockY);

                // Initialiser les nouveaux blocs dans la nouvelle zone visible
                OnLoad(shader);
            }
        }

        private int ConvertPositionToLandBlockCoord(float position) {
            // Convertir la position en coordonnée de landblock, ajuster selon la mise en échelle de votre carte
            return (int)(position / 128) + 0x7f; // Ajustez cette conversion en fonction de votre mise en échelle
        }
        private bool IsInNewRadius(int x, int y, int newLandBlockX, int newLandBlockY) {
            // Calculer le nouveau startX, startY, endX, et endY basé sur newLandBlockX, newLandBlockY
            int newStartX = Math.Max(0, newLandBlockX - radius);
            int newStartY = Math.Max(0, newLandBlockY - radius);
            int newEndX = Math.Min(NumberLandBlocks - 1, newLandBlockX + radius);
            int newEndY = Math.Min(NumberLandBlocks - 1, newLandBlockY + radius);

            // Vérifier si (x, y) est dans la nouvelle zone définie par newStartX, newStartY, newEndX, et newEndY
            return x >= newStartX && x <= newEndX && y >= newStartY && y <= newEndY;
        }
    }
}
