package xaero.rotatenjump.game.graphics.model;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;

import xaero.rotatenjump.game.graphics.Texture;

/**
 * Model loaded from a file
 * Created by xaero on 23/01/2018.
 */

public class FileModel extends Model {

    public FileModel(InputStream in){
        Texture texture = null;//doesn't really support textures yet
        ArrayList<short[]> indexList = new ArrayList<>();//element: {index, original index}
        ArrayList<float[]> originalVertices = new ArrayList<>();//vertices loaded from the file
        ArrayList<float[]> originalNormals = new ArrayList<>();//normals loaded from the file
        ArrayList<float[]> vertices = new ArrayList<>();//vertex list with duplicates
        ArrayList<float[]> normals = new ArrayList<>();//vertex normals list corresponding to the "vertices" list
        ArrayList<float[]> colours = new ArrayList<>();//vertex colours list corresponding to the "vertices" list
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        float[] currentColour = {1,1,1,1};
        try {
            String line = null;
            while((line = reader.readLine()) != null){
                String[] args = line.split(" ");
                if(args.length == 0)
                    continue;
                if(args[0].equals("v"))
                    originalVertices.add(new float[]{Float.parseFloat(args[1]), Float.parseFloat(args[2]), Float.parseFloat(args[3])});
                //else if(args[0].equals("vn"))
                //    originalNormals.add(new float[]{Float.parseFloat(args[1]), Float.parseFloat(args[2]), Float.parseFloat(args[3])});
                else if(args[0].equals("c")) {
                    currentColour = new float[]{1,1,1,1};
                    for (int i = 0; i < currentColour.length; i++)
                        if (i + 1 < args.length)
                            currentColour[i] = Float.parseFloat(args[i + 1]);
                }
                else if(args[0].equals("f")){
                    short[][] faceIndexes = new short[3][3];
                    for(int i = 0; i < 3; i++) {
                        String[] split = args[i + 1].split("/");
                        for(int j = 0; j < split.length; j++)
                            if(!split[j].isEmpty())
                                faceIndexes[i][j] = (short)(Integer.parseInt(split[j]) - 1);
                    }
                    float[] normal = null;
                    if(originalNormals.isEmpty())//normals not included in the file
                        normal = calculateNormal(faceIndexes, originalVertices);
                    for(int i = 0; i < 3; i++){
                        short originalVertexIndex = faceIndexes[i][0];
                        short originalNormalIndex = faceIndexes[i][2];
                        short index = getIndexForCalculatedNormalAndColour(originalVertexIndex, normal, currentColour, normals, colours, indexList);
                        if(index == -1) {
                            index = (short)vertices.size();
                            vertices.add(originalVertices.get(originalVertexIndex));//creates a duplicate vertex for the new normal
                            if(normal == null)//no normal calculated, take from file
                                normals.add(originalNormals.get(originalNormalIndex));
                            else
                                normals.add(normal);//corresponding normal
                            colours.add(currentColour);//corresponding colour
                        }
                        indexList.add(new short[]{originalVertexIndex, index});
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        float[] vertexArray = new float[vertices.size() * VERTEX_SIZE];
        short[] indexArray = new short[indexList.size()];
        for(int i = 0; i < vertices.size(); i++) {
            int offset = i * VERTEX_SIZE;
            System.arraycopy(vertices.get(i), 0, vertexArray, offset, 3);
            System.arraycopy(normals.get(i), 0, vertexArray, offset + 5, 3);
            //System.out.println(Arrays.toString(colours.get(i)));
            System.arraycopy(colours.get(i), 0, vertexArray, offset + 8, 4);
        }
        for(int i = 0; i < indexList.size(); i++)
            indexArray[i] = indexList.get(i)[1];
        create(vertexArray, indexArray, texture);
    }

    /*
     * Returns the index of a previously created vertex if the normals and colours match, returns -1 otherwise which means that a duplicate is required
     */
    private short getIndexForCalculatedNormalAndColour(int originalIndex, float[] normal, float[] colour, ArrayList<float[]> normals, ArrayList<float[]> colours, ArrayList<short[]> indexList){
        for(int i = 0; i < indexList.size(); i++){
            short[] previousUsage = indexList.get(i);
            if(previousUsage[0] == originalIndex){
                if(normal == null || Arrays.equals(normal, normals.get(previousUsage[1]))) {//comparing with previously used normal
                    if(Arrays.equals(colour, colours.get(previousUsage[1]))) {//comparing with previously used colour
                        //System.out.println("Reusing vertex: " + (previousUsage[0] + 1));
                        return previousUsage[1];//use the same index (less duplicates)
                    }
                }
            }
        }
        return -1;
    }

    private float[] calculateNormal(short[][] faceIndexes, ArrayList<float[]> originalVertices){
        float[] result = new float[3];
        float[] v1 = originalVertices.get(faceIndexes[0][0]);
        float[] v2 = originalVertices.get(faceIndexes[1][0]);
        float[] v3 = originalVertices.get(faceIndexes[2][0]);
        float[] vector1 = {v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]};
        float[] vector2 = {v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2]};
        result[0] = vector1[1] * vector2[2] - vector1[2] * vector2[1];
        result[1] = vector1[2] * vector2[0] - vector1[0] * vector2[2];
        result[2] = vector1[0] * vector2[1] - vector1[1] * vector2[0];
        for(int i = 0; i < 3; i++)//rounding the results so that more normals match
            result[i] = Math.round(result[i] * 10000) / 10000.0f;
        //System.out.println("Calculated normal: " + Arrays.toString(result) + " for face " + Arrays.toString(faceIndexes));
        return result;
    }

}
