import {
    Loader,
    FileLoader,
    BufferGeometry,
    Float32BufferAttribute,
    Uint32BufferAttribute,
    LineSegments,
    LineBasicMaterial,
    WireframeGeometry
} from '../../../assets/lib/vendor/three/three.module.js';

class MeshLoader extends Loader {
    constructor(manager, material) {
        super(manager);
        this.material = material || new LineBasicMaterial({ color: 0x878990 });
    }

    load(url, onLoad, onProgress, onError) {
        new FileLoader(this.manager).setPath(this.path).load(
            url,
            text => {
                try {
                    const { geometry, isSurface } = this.parse(text);
                    const mesh = isSurface
                          ? new LineSegments(new WireframeGeometry(geometry), this.material)
                          : new LineSegments(geometry, this.material);
                    onLoad(mesh);
                } catch (e) {
                    onError?.(e);
                }
            },
            onProgress,
            onError
        );
    }

    parse(text) {
        const lines = text.split(/\r?\n/)
              .map(l => l.split('#')[0].trim())
              .filter(Boolean);

        let i = 0;
        if (lines[i].startsWith('MeshVersionFormatted')) i++;
        if (lines[i] === 'Dimension') i += 2;
        if (lines[i++] !== 'Vertices') throw new Error('Missing Vertices');

        const nV = +lines[i++];
        const positions = new Float32Array(nV * 3);
        for (let v = 0; v < nV; v++) {
            const [x, y, z] = lines[i++].split(/\s+/).map(Number);
            positions.set([x, y, z], v * 3);
        }

        const triangles = [];
        const tetra = [];
        const quads = [];
        const hexes = [];

        while (i < lines.length) {
            const sec = lines[i++];
            const n = +lines[i++];
            if (sec === 'Triangles') {
                for (let k = 0; k < n; k++) {
                    triangles.push(lines[i++].split(/\s+/).map(n => +n - 1).slice(0, 3));
                }
            } else if (sec === 'Tetrahedra') {
                for (let k = 0; k < n; k++) {
                    tetra.push(lines[i++].split(/\s+/).map(n => +n - 1).slice(0, 4));
                }
            } else if (sec === 'Quadrilaterals') {
                for (let k = 0; k < n; k++) {
                    quads.push(lines[i++].split(/\s+/).map(n => +n - 1).slice(0, 4));
                }
            } else if (sec === 'Hexahedra') {
                for (let k = 0; k < n; k++) {
                    hexes.push(lines[i++].split(/\s+/).map(n => +n - 1).slice(0, 8));
                }
            } else {
                break;
            }
        }

        const geom = new BufferGeometry();
        geom.setAttribute('position', new Float32BufferAttribute(positions, 3));

        if (triangles.length) {
            const idx = new Uint32Array(triangles.length * 3);
            triangles.forEach((tri, j) => idx.set(tri, j * 3));
            geom.setIndex(new Uint32BufferAttribute(idx, 1));
            return { geometry: geom, isSurface: true };
        }

        const edgeSet = new Set();
        const addEdge = (a, b) => {
            edgeSet.add(a < b ? `${a},${b}` : `${b},${a}`);
        };

        tetra.forEach(e => [[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]].forEach(([u,v]) => addEdge(e[u], e[v])));
        quads.forEach(e => [[0,1],[1,2],[2,3],[3,0]].forEach(([u,v]) => addEdge(e[u], e[v])));
        hexes.forEach(e => [[0,1],[1,2],[2,3],[3,0],[4,5],[5,6],[6,7],[7,4],[0,4],[1,5],[2,6],[3,7]].forEach(([u,v]) => addEdge(e[u], e[v])));

        const edges = new Uint32Array(edgeSet.size * 2);
        let p = 0;
        edgeSet.forEach(k => {
            const [a, b] = k.split(',').map(Number);
            edges[p++] = a;
            edges[p++] = b;
        });
        geom.setIndex(new Uint32BufferAttribute(edges, 1));

        return { geometry: geom, isSurface: false };
    }
}

export { MeshLoader };