package me.earthme.luminol.collision.backend;

import me.earthme.luminol.collision.CollisionPushResult;
import me.earthme.luminol.collision.INativeCollisionBackend;
import me.earthme.luminol.config.modules.optimizations.AcceleratedCollisionConfig;

import java.util.ArrayList;
import java.util.List;

public class JavaCollisionBackend implements INativeCollisionBackend {

    private static final int TABLE_SIZE = 262139;
    private static final double CELL_SIZE = 2.5;

    @Override public String getName() { return "Java"; }
    @Override public void initialize() {}
    @Override public void applyConfig() {}
    @Override public void destroy() {}

    private record Pair(int a, int b) {}

    @Override
    public CollisionPushResult push(double[] aabb, int[] resultSizeOut) {
        int count = aabb.length / 6;
        if (count == 0) { resultSizeOut[0] = 0; return null; }

        int maxCollision = AcceleratedCollisionConfig.maxCollision;
        double cellSize = Math.max(1, AcceleratedCollisionConfig.gridSize) * CELL_SIZE;

        @SuppressWarnings("unchecked")
        List<Integer>[] table = new List[TABLE_SIZE];

        for (int i = 0; i < count; i++) {
            double cx = (aabb[i*6] + aabb[i*6+3]) * 0.5;
            double cy = (aabb[i*6+1] + aabb[i*6+4]) * 0.5;
            double cz = (aabb[i*6+2] + aabb[i*6+5]) * 0.5;
            int h = hash((int)Math.floor(cx/cellSize), (int)Math.floor(cy/cellSize), (int)Math.floor(cz/cellSize));
            if (table[h] == null) table[h] = new ArrayList<>();
            table[h].add(i);
        }

        List<Pair> pairs = new ArrayList<>();
        float[] density = new float[count];

        for (int i = 0; i < count; i++) {
            double minX = aabb[i*6], minY = aabb[i*6+1], minZ = aabb[i*6+2];
            double maxX = aabb[i*6+3], maxY = aabb[i*6+4], maxZ = aabb[i*6+5];
            int gx = (int)Math.floor(((minX+maxX)*0.5)/cellSize);
            int gy = (int)Math.floor(((minY+maxY)*0.5)/cellSize);
            int gz = (int)Math.floor(((minZ+maxZ)*0.5)/cellSize);

            int localDensity = 0;
            for (int dz = -1; dz <= 1; dz++) for (int dy = -1; dy <= 1; dy++) for (int dx = -1; dx <= 1; dx++) {
                List<Integer> cell = table[hash(gx+dx, gy+dy, gz+dz)];
                if (cell == null) continue;
                for (int j : cell) {
                    if (j == i) continue;
                    if (minX > aabb[j*6+3] || maxX < aabb[j*6]) continue;
                    if (minY > aabb[j*6+4] || maxY < aabb[j*6+1]) continue;
                    if (minZ > aabb[j*6+5] || maxZ < aabb[j*6+2]) continue;
                    localDensity++;
                    if (i < j && pairs.size() < count * maxCollision) pairs.add(new Pair(i, j));
                }
            }
            density[i] = localDensity;
        }

        int pairCount = pairs.size();
        resultSizeOut[0] = pairCount;
        if (pairCount == 0) return null;

        int[] arrA = new int[pairCount], arrB = new int[pairCount];
        for (int k = 0; k < pairCount; k++) { arrA[k] = pairs.get(k).a(); arrB[k] = pairs.get(k).b(); }
        float[] finalDensity = density;

        return new CollisionPushResult() {
            @Override public int getA(int idx) { return arrA[idx]; }
            @Override public int getB(int idx) { return arrB[idx]; }
            @Override public float getDensity(int idx) { return finalDensity[idx]; }
            @Override public void copyATo(int[] dest, int len) { System.arraycopy(arrA,0,dest,0,len); }
            @Override public void copyBTo(int[] dest, int len) { System.arraycopy(arrB,0,dest,0,len); }
            @Override public void copyDensityTo(float[] dest, int len) { System.arraycopy(finalDensity,0,dest,0,len); }
        };
    }

    @Override
    public int pushInto(double[] aabb, int count, int[] outA, int[] outB) {
        if (count == 0) return -1;
        int[] size = new int[1];
        CollisionPushResult r = push(aabb, size);
        if (r == null || size[0] == 0) return 0;
        int n = Math.min(size[0], Math.min(outA.length, outB.length));
        r.copyATo(outA, n);
        r.copyBTo(outB, n);
        return n;
    }

    private static int hash(int x, int y, int z) {
        return Math.abs((Math.abs(x)*73856093)^(Math.abs(y)*19349663)^(Math.abs(z)*83492791)) % TABLE_SIZE;
    }
}
