package Graph;

import java.io.*;
import java.util.*;
import java.util.Random;

/**
 * readFile class utilized to parse through commands to obtain the information of the figure
 */
public class readFile {
    private String path;
    private static double speed;
    private static double[] direction;
    private static int index, vertexCount;
    public static Map<VertexKey, ArrayList<VertexKey>> vertexList;
    public static Map<VertexKey,Integer> vertexToIndex;
    public static Map<Integer, VertexKey> indexToVertex;

    public static float[] vertices;

    private static ArrayList<Integer> faces;

    public static AdjacencyMatrix adjMatrix;


    public readFile() {
        direction = new double[3];
        vertexList = new HashMap<>();
        vertexToIndex = new HashMap<>();
        indexToVertex = new HashMap<>();
        faces = new ArrayList<>();
        index = 0;
        vertexCount = 0;
    }
    /**
     * Parses an array of command-line arguments to extract image paths, speeds, and directional vectors.
     * This method expects arguments in specific formats:
     *
     * <ul>
     *  <li>{@code --image} followed by a string representing the path to an image.</li>
     *  <li>{@code --speed} followed by a double value representing the speed.</li>
     *  <li>{@code --dir} followed by three comma-separated double values representing a directional vector.</li>
     * </ul>
     *
     * @param args args the command-line arguments to be parsed.
     */

    public readFile(String[] args) {
        this();
        for (String arg : args) {
            if (Objects.equals(arg, "--image")) {
                path = args[index + 1];
            }
            if (Objects.equals(arg, "--speed")) {
                speed = Double.parseDouble(args[index + 1]);
            }
            if (Objects.equals(arg, "--dir")) {
                double mag = 0;
                for (int ii = 0; ii < 3; ii++) {
                    direction[ii] = Double.parseDouble(args[index + ii + 1].replace(",",""));
                    mag += Math.pow(direction[ii], 2);
                }
                mag = Math.sqrt(mag);
                for (int ii = 0; ii < 3; ii++) {
                    direction[ii] = direction[ii]/mag * speed;
                }
            }
            index++;
        }
        System.out.println("Image path: " + path);
        System.out.println("Speed: " + speed);
        System.out.println("Direction: " + Arrays.toString(direction));
    }
    public readFile(String filePath) {
        this.path = filePath;
        for (int i = 0; i < 3; i++) {
            direction[i] = 0;
        }
        speed = 0;
        this.makeVertexHash(path);
    }

    public String getPath() {return this.path;}
    public Map<Integer,VertexKey> getMapping() {return indexToVertex;}

    public double[] getDirection() {return direction;}
    /**
     * Reads vertex data from a specified file and populates global hash structures
     * to map each vertex to its adjacent vertices and indices. The file should contain
     * descriptions of triangles, with each line representing a triangle. Vertices are
     * expected to be in the format "[x,y,z]", separated by spaces.
     *
     * @param currPath the path to the file containing the triangles' vertex data.
     * @throws FileNotFoundException if the file at {@code currPath} does not exist or cannot be read.
     */
    public void makeVertexHash(String currPath) {
        try
        {
            File fileObj = new File(currPath);
            Scanner myReader = new Scanner(fileObj);
            while (myReader.hasNextLine()) {
                String data = myReader.nextLine();
                String[] coords = data.split(" ");
                ArrayList<float[]> triangle = new ArrayList<>();
                for (String coord:coords) {
                    coord = coord.replaceAll("[\\[\\]]", "");
                    String[] vals = coord.split(",");
                    int valIndex = 0;
                    float[] vertex = new float[3];
                    for (String val:vals) {
                        vertex[valIndex] = Float.parseFloat(val);
                        valIndex++;
                    }
                    triangle.add(vertex);
                }
                for (int i = 0; i<3; i++) {
                    float[] vertex = triangle.get(i);
                    VertexKey vertexKey = new VertexKey(vertex);
                    if (!vertexList.containsKey(vertexKey)) {
                        ArrayList<VertexKey> adjList = new ArrayList<>();
                        vertexList.put(vertexKey, adjList);
                        vertexToIndex.put(vertexKey, vertexCount);
                        indexToVertex.put(vertexCount, vertexKey);
                        vertexCount++;
                    }
                    faces.add(vertexToIndex.get(vertexKey));
                    for (int j = 1; j < 3; j++) {
                        VertexKey addVertex = new VertexKey(triangle.get((i+j) % 3));
                        if (!vertexList.get(vertexKey).contains(addVertex))
                            vertexList.get(vertexKey).add(addVertex);
                    }
                }
            }

            myReader.close();
        }
        catch (FileNotFoundException exception) {

        }
    }

    /**
     * Constructs an adjacency matrix for a graph using pre-existing vertex and adjacency information.
     * This method initializes a new {@link AdjacencyMatrix} instance and populates it with edges based
     * on the adjacency information stored in a global structure.
     * <p>
     * It iterates over all vertices, represented by their indices, and for each vertex, it retrieves its
     * corresponding {@link VertexKey}. It then iterates over all vertices adjacent to the current vertex,
     * adding an edge to the adjacency matrix for each adjacency. This process constructs a complete
     * representation of the graph's connectivity in matrix form.
     */
    public void makeMatrix() {
        adjMatrix = new AdjacencyMatrix(vertexCount);
        for (int i = 0; i < vertexCount; i++) {
            VertexKey currVertexKey = indexToVertex.get(i);
            for (VertexKey adjVertex : vertexList.get(currVertexKey)) {
                adjMatrix.addEdge(i,vertexToIndex.get(adjVertex));
            }
        }
    }
    /**
     * Retrieves all vertices from the graph, scaled by a factor of 3, and returns them as a flat array.
     * <p>
     * This method constructs an array of floating-point numbers, where each set of three consecutive
     * entries represents the x, y, and z coordinates of a vertex, respectively, scaled by a factor of 3.
     * The vertices are retrieved in the order determined by their indices in the {@code indexToVertex} mapping.
     * <p>
     * Note: The method assumes that each {@link VertexKey} object's {@code get()} method returns an
     * array of three floats representing the x, y, and z coordinates of the vertex.
     *
     * @return A flat array of floats representing the scaled coordinates of all vertices in the graph.
     *         Each vertex's coordinates occupy three consecutive positions in the array (x, y, z),
     *         scaled by a factor of 3.
     */
    public float[] getVertices() {
        float[] vertices = new float[vertexCount * 3];
        VertexKey tmp;
        int count = 0;
        for (int ii = 0; ii < indexToVertex.size(); ii++ ) {
            tmp = indexToVertex.get(ii);
            for (float jj : tmp.get()) {
                vertices[count] = jj *3;
                count++;
            }
        }
        return vertices;
    }
    /**
     * Generates an array of face indices with corresponding texture indices for a graphical object.
     * <p>
     * This method constructs an array where each pair of elements corresponds to a face index
     * followed by a texture index. Given that the texture index is not specified or used (set to 0),
     * this method effectively doubles the size of the input list, with every second element in the
     * output array being 0.
     * <p>
     * The method iterates over a global {@code faces} collection, where each element represents a
     * single face index. For each face index from this collection, two entries are added to the result
     * array: the face index itself and a 0 indicating the default or placeholder texture index.
     *
     * @return An array of integers where for every face index from the global faces collection,
     *         two consecutive entries are created in the output array: the face index and a 0 as a
     *         placeholder for the texture index.
     */
    public int[] getFaces() {

        int[] facesWithTexture = new int[faces.size()*2];
        int count = 0;
        for (int face : faces) {
            facesWithTexture[count*2] = face;
            facesWithTexture[count*2 + 1] = 0;
            count++;
        }
        return facesWithTexture;
    }

    /**
     * Generates a file with a specified filename, populated with 15,000 randomly generated lines of data.
     * Each line is created by the {@code makeLine} method, formatting random coordinate triples to simulate
     * vertices or points in a 3D space.
     * <p>
     * This method creates or overwrites a file named according to the {@code filename} parameter. It uses
     * a {@link FileWriter} to efficiently write lines to the file. Each line represents a collection of
     * three coordinate points, each point consisting of x, y, and z coordinates generated randomly within a
     * range of 0 to 30 (exclusive).
     *
     * @param filename The name of the file to be generated or overwritten. If the file already exists,
     *                 its contents will be replaced with the new, randomly generated data.
     * @throws IOException If an I/O error occurs during writing to the file.
     */
    public void randFileGenerator(String filename) throws IOException {
        Random random = new Random();
        try (FileWriter writer = new FileWriter(filename)) {
                for (int i = 0; i < 15000; i++) {
                    String face = makeLine(random);
                    writer.write(face);
                    writer.write(System.lineSeparator());
                    writer.flush();
                }
            }

    }
    /**
     * Generates a string representing three 3D points, each with x, y, and z coordinates.
     * The coordinates are randomly generated within the range [0, 30) and formatted into a string.
     * <p>
     * This method is utilized by {@code randFileGenerator} to create each line of the output file, simulating
     * vertices of triangles in a 3D space. The randomness introduces variability in the data.
     *
     * @param random A {@link Random} instance used to generate the random floats for the coordinates.
     * @return A string formatted as "[x1,y1,z1] [x2,y2,z2] [x3,y3,z3]", where x, y, and z are coordinate
     *         values for three points, generated randomly.
     */
    private String makeLine(Random random) {
        return String.format("[%f,%f,%f] [%f,%f,%f] [%f,%f,%f]",
                vertices[random.nextInt(vertices.length / 3) * 3], vertices[random.nextInt(vertices.length / 3) * 3], vertices[random.nextInt(vertices.length / 3) * 3],
                vertices[random.nextInt(vertices.length / 3) * 3], vertices[random.nextInt(vertices.length / 3) * 3], vertices[random.nextInt(vertices.length / 3) * 3],
                vertices[random.nextInt(vertices.length / 3) * 3], vertices[random.nextInt(vertices.length / 3) * 3], vertices[random.nextInt(vertices.length / 3) * 3]);
    }
    /**
     * Generates a specified number of random vertices within a 3D space, each represented by x, y, and z coordinates.
     * The coordinates are generated within a bounded range [0, 30) and stored in a global float array. This method
     * fills the {@code vertices} array with float values where every set of three floats represents the coordinates
     * of a single vertex.
     *
     * @param numVertices The number of vertices to generate. This determines the size of the {@code vertices} array.
     */
    public void generateRandomVertices(int numVertices) {
        float range = 30;
        Random random = new Random();
        vertices = new float[numVertices * 3];
        for (int i = 0; i < numVertices * 3; i += 3) {
            float x = (random.nextFloat() * range);
            float y = (random.nextFloat() * range);
            float z = (random.nextFloat() * range);

            // Ensures the value stays within range
            vertices[i] = Math.max(0, Math.min(range, x));
            vertices[i + 1] = Math.max(0, Math.min(range, y));
            vertices[i + 2] = Math.max(0, Math.min(range, z));
        }
    }
}
