package electrosphere;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

import org.joml.Vector3i;

import electrosphere.render.GLFWContext;
import electrosphere.render.Mesh;

/**
 *
 */
public class Main {

    static boolean render = false;
    public static int endStep = -1;

    public static final float TIMESTEP = 0.001f;
    

    public static void main(String args[]){

        int dim = 1;
        int vdim = 1;
        int i = 0;
        long time = 0;
        long lastTime = 0;
        Scanner scan = new Scanner(System.in);

        try {

            if(render){
                GLFWContext.init();

                //init shader program
                Mesh.initShaderProgram();
            }


            FluidSim[][][] simArray = FluidSim.initFluidSim(dim,vdim,dim);
            
            Mesh[][][] meshArray = null;
            if(render){
                meshArray = initMeshes(dim,vdim,dim,simArray);
            }


            //uncomment this to generate test data
            // generateTestData();

            while(true){
                try {
                    TimeUnit.MILLISECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //
                //Transfer data
                //
                lastTime = System.currentTimeMillis();
                //
                //Simulate
                //
                FluidSim.simChunks(simArray,i,TIMESTEP);
                //
                //Remesh
                //
                if(render){
                    for(int x = 0; x < simArray.length; x++){
                        for(int y = 0; y < simArray[0].length; y++){
                            for(int z = 0; z < simArray[0][0].length; z++){
                                meshArray[x][y][z].remesh();
                            }
                        }
                    }
                }
                time = time + (System.currentTimeMillis() - lastTime);
                //redraw
                if(render){
                    GLFWContext.redraw(meshArray);
                }
                i++;
                if(i == 100){
                    System.out.println("overall time: " + time / 100.0);
                }
                if(i > 3){
                    // scan.next();
                }
            }
        } catch(Throwable ex){
            ex.printStackTrace(System.err);
        }
    }

    private static Mesh[][][] initMeshes(int dimx, int dimy, int dimz, FluidSim[][][] simArray){
        Mesh[][][] meshArray = new Mesh[dimx][dimy][dimz];
        for(int x = 0; x < simArray.length; x++){
            for(int y = 0; y < simArray[0].length; y++){
                for(int z = 0; z < simArray[0][0].length; z++){
                    meshArray[x][y][z] = new Mesh(simArray[x][y][z], new Vector3i(z * 16, y * 16, x * 16));
                    meshArray[x][y][z].meshInitially();
                }
            }
        }
        return meshArray;
    }

    /**
     * Generates test data on disk in the test resources folder
     */
    private static void generateTestData(){

        //variables that will be used in generating data
        int i = 0;
        int dim = 1;
        int length = 500;
        FluidSim[][][] simArray;
        int[] dims = new int[]{1,3,5};
        int[] lengths = new int[]{1,50,100,500};
        render = false;

        //clean up existing data
        //this is scary recursive file logic, but the innermost callback has a guard to guarantee the file is underneath the testdata path
        while(Files.exists(new File("./src/test/resources/testdata").toPath())){
            try {
                Files.walk(new File("./src/test/resources/testdata").toPath()).forEach(path -> {
                    Path parent = new File("./src/test/resources/testdata").toPath().toAbsolutePath();
                    Path child = path.toAbsolutePath();
                    try {
                        TimeUnit.MILLISECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    if(child.startsWith(parent)){
                        try {
                            System.out.println("Deleting " + path);
                            Files.delete(path);
                        } catch (IOException e) {
                            System.out.println("Failed to delete " + path);
                            e.printStackTrace();
                        }
                    }
                });
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        //create folders to contain test data
        try {
            Files.createDirectory(new File("./src/test/resources/testdata").toPath());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for(int dimIterator = 0; dimIterator < dims.length; dimIterator++){
            dim = dims[dimIterator];
            try {
                Files.createDirectory(new File("./src/test/resources/testdata/" + dim + "by" + dim).toPath());
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for(int lengthIterator = 0; lengthIterator < lengths.length; lengthIterator++){
                length = lengths[lengthIterator];
                try {
                    Files.createDirectory(new File("./src/test/resources/testdata/" + dim + "by" + dim + "/" + length + "steps").toPath());
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        //main data generation loop
        for(int dimIterator = 0; dimIterator < dims.length; dimIterator++){
            for(int lengthIterator = 0; lengthIterator < lengths.length; lengthIterator++){


                //actual main routine to generate data starts here
                dim = dims[dimIterator];
                length = lengths[lengthIterator];
                System.out.println("Generating " + dim + "x" + dim + "x" + dim + " for " + length + " steps");

                i = 0;
                simArray = FluidSim.initFluidSim(dim,dim,dim);
                for(i = 0; i < length; i++){
                    FluidSim.simChunks(simArray,i,TIMESTEP);
                }

                for(int x = 0; x < dim; x++){
                    for(int y = 0; y < dim; y++){
                        for(int z = 0; z < dim; z++){
                            simArray[x][y][z].dumpToDisk("./src/test/resources/testdata/" + dim + "by" + dim + "/" + length + "steps/chunk_" + x + "_" + y + "_" + z + "_" + dim + "by" + dim + "Chunk" + length + "Step.data");
                        }
                    }
                }

                for(int x = 0; x < dim; x++){
                    for(int y = 0; y < dim; y++){
                        for(int z = 0; z < dim; z++){
                            byte[] bytes;
                            try {
                                bytes = Files.readAllBytes(new File("./src/test/resources/testdata/" + dim + "by" + dim + "/" + length + "steps/chunk_" + x + "_" + y + "_" + z + "_" + dim + "by" + dim + "Chunk" + length + "Step.data").toPath());
                                ByteBuffer densityBytes = simArray[0][0][0].getDensityBuffer();
                                i = 0;
                                while(densityBytes.hasRemaining()){
                                    boolean pass = bytes[i] == densityBytes.get();
                                    if(!pass){
                                        System.err.println("failed to pass!");
                                    }
                                    i++;
                                }
                            } catch (IOException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }


        System.exit(0);
    }

}
