// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.builtin.buildertools.objimport;

import java.util.Iterator;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.BufferedReader;
import java.util.List;
import java.nio.file.Files;
import java.util.ArrayList;
import javax.annotation.Nonnull;
import java.nio.file.Path;

public final class ObjParser
{
    private ObjParser() {
    }
    
    @Nonnull
    public static ObjMesh parse(@Nonnull final Path path) throws IOException, ObjParseException {
        final List<float[]> vertices = new ArrayList<float[]>();
        final List<float[]> uvCoordinates = new ArrayList<float[]>();
        final List<int[]> faces = new ArrayList<int[]>();
        final List<int[]> faceUvIndices = new ArrayList<int[]>();
        final List<String> faceMaterials = new ArrayList<String>();
        String mtlLib = null;
        String currentMaterial = null;
        try (final BufferedReader reader = Files.newBufferedReader(path)) {
            int lineNum = 0;
            String line;
            while ((line = reader.readLine()) != null) {
                ++lineNum;
                line = line.trim();
                if (!line.isEmpty()) {
                    if (line.startsWith("#")) {
                        continue;
                    }
                    final String[] parts = line.split("\\s+");
                    if (parts.length == 0) {
                        continue;
                    }
                    final String s = parts[0];
                    switch (s) {
                        case "v": {
                            parseVertex(parts, vertices, lineNum);
                            continue;
                        }
                        case "vt": {
                            parseUvCoordinate(parts, uvCoordinates, lineNum);
                            continue;
                        }
                        case "f": {
                            final int faceCountBefore = faces.size();
                            parseFace(parts, faces, faceUvIndices, uvCoordinates.size(), lineNum);
                            for (int facesAdded = faces.size() - faceCountBefore, i = 0; i < facesAdded; ++i) {
                                faceMaterials.add(currentMaterial);
                            }
                            continue;
                        }
                        case "mtllib": {
                            if (parts.length > 1) {
                                mtlLib = parts[1].trim();
                                continue;
                            }
                            continue;
                        }
                        case "usemtl": {
                            if (parts.length > 1) {
                                currentMaterial = parts[1].trim();
                                continue;
                            }
                            continue;
                        }
                    }
                }
            }
        }
        if (vertices.isEmpty()) {
            throw new ObjParseException("OBJ file contains no vertices");
        }
        if (faces.isEmpty()) {
            throw new ObjParseException("OBJ file contains no faces");
        }
        return new ObjMesh(vertices, uvCoordinates, faces, faceUvIndices, faceMaterials, mtlLib);
    }
    
    private static void parseVertex(final String[] parts, final List<float[]> vertices, final int lineNum) throws ObjParseException {
        if (parts.length < 4) {
            throw new ObjParseException("Invalid vertex at line " + lineNum + ": expected at least 3 coordinates");
        }
        try {
            final float x = Float.parseFloat(parts[1]);
            final float y = Float.parseFloat(parts[2]);
            final float z = Float.parseFloat(parts[3]);
            vertices.add(new float[] { x, y, z });
        }
        catch (final NumberFormatException e) {
            throw new ObjParseException("Invalid vertex coordinates at line " + lineNum);
        }
    }
    
    private static void parseUvCoordinate(final String[] parts, final List<float[]> uvCoordinates, final int lineNum) throws ObjParseException {
        if (parts.length < 3) {
            throw new ObjParseException("Invalid UV coordinate at line " + lineNum + ": expected at least 2 values");
        }
        try {
            final float u = Float.parseFloat(parts[1]);
            final float v = Float.parseFloat(parts[2]);
            uvCoordinates.add(new float[] { u, v });
        }
        catch (final NumberFormatException e) {
            throw new ObjParseException("Invalid UV coordinates at line " + lineNum);
        }
    }
    
    private static void parseFace(final String[] parts, final List<int[]> faces, final List<int[]> faceUvIndices, final int uvCount, final int lineNum) throws ObjParseException {
        if (parts.length < 4) {
            throw new ObjParseException("Invalid face at line " + lineNum + ": expected at least 3 vertices");
        }
        final int[] vertexIndices = new int[parts.length - 1];
        final int[] uvIndices = new int[parts.length - 1];
        boolean hasUvs = false;
        for (int i = 1; i < parts.length; ++i) {
            final String vertexData = parts[i];
            final String[] components = vertexData.split("/");
            try {
                final int vIndex = Integer.parseInt(components[0]);
                vertexIndices[i - 1] = ((vIndex > 0) ? (vIndex - 1) : vIndex);
            }
            catch (final NumberFormatException e) {
                throw new ObjParseException("Invalid face vertex index at line " + lineNum);
            }
            if (components.length >= 2 && !components[1].isEmpty()) {
                try {
                    final int uvIndex = Integer.parseInt(components[1]);
                    uvIndices[i - 1] = ((uvIndex > 0) ? (uvIndex - 1) : (uvIndex + uvCount));
                    hasUvs = true;
                }
                catch (final NumberFormatException e) {
                    uvIndices[i - 1] = -1;
                }
            }
            else {
                uvIndices[i - 1] = -1;
            }
        }
        if (vertexIndices.length == 3) {
            faces.add(vertexIndices);
            faceUvIndices.add((int[])(hasUvs ? uvIndices : null));
        }
        else if (vertexIndices.length == 4) {
            faces.add(new int[] { vertexIndices[0], vertexIndices[1], vertexIndices[2] });
            faces.add(new int[] { vertexIndices[0], vertexIndices[2], vertexIndices[3] });
            if (hasUvs) {
                faceUvIndices.add(new int[] { uvIndices[0], uvIndices[1], uvIndices[2] });
                faceUvIndices.add(new int[] { uvIndices[0], uvIndices[2], uvIndices[3] });
            }
            else {
                faceUvIndices.add(null);
                faceUvIndices.add(null);
            }
        }
        else {
            for (int i = 1; i < vertexIndices.length - 1; ++i) {
                faces.add(new int[] { vertexIndices[0], vertexIndices[i], vertexIndices[i + 1] });
                if (hasUvs) {
                    faceUvIndices.add(new int[] { uvIndices[0], uvIndices[i], uvIndices[i + 1] });
                }
                else {
                    faceUvIndices.add(null);
                }
            }
        }
    }
    
    record ObjMesh(List<float[]> vertices, List<float[]> uvCoordinates, List<int[]> faces, List<int[]> faceUvIndices, List<String> faceMaterials, @Nullable String mtlLib) {
        public float[] getBounds() {
            float minX = Float.MAX_VALUE;
            float minY = Float.MAX_VALUE;
            float minZ = Float.MAX_VALUE;
            float maxX = -3.4028235E38f;
            float maxY = -3.4028235E38f;
            float maxZ = -3.4028235E38f;
            for (final float[] v : this.vertices) {
                minX = Math.min(minX, v[0]);
                minY = Math.min(minY, v[1]);
                minZ = Math.min(minZ, v[2]);
                maxX = Math.max(maxX, v[0]);
                maxY = Math.max(maxY, v[1]);
                maxZ = Math.max(maxZ, v[2]);
            }
            return new float[] { minX, minY, minZ, maxX, maxY, maxZ };
        }
        
        public float getHeight() {
            final float[] bounds = this.getBounds();
            return bounds[4] - bounds[1];
        }
        
        public boolean hasMaterials() {
            return this.mtlLib != null && !this.faceMaterials.isEmpty() && this.faceMaterials.stream().anyMatch(m -> m != null);
        }
        
        public boolean hasUvCoordinates() {
            return !this.uvCoordinates.isEmpty() && this.faceUvIndices.stream().anyMatch(uv -> uv != null);
        }
        
        public void transformZUpToYUp() {
            for (final float[] v : this.vertices) {
                final float y = v[1];
                final float z = v[2];
                v[1] = z;
                v[2] = -y;
            }
        }
        
        public void transformXUpToYUp() {
            for (final float[] v : this.vertices) {
                final float x = v[0];
                final float y = v[1];
                v[0] = y;
                v[1] = x;
            }
        }
        
        @Nullable
        public String mtlLib() {
            return this.mtlLib;
        }
    }
    
    public static class ObjParseException extends Exception
    {
        public ObjParseException(final String message) {
            super(message);
        }
    }
}
