package electrosphere.main;

import com.google.gson.Gson;
import electrosphere.audio.AudioEngine;
import electrosphere.audio.AudioSource;
import electrosphere.audio.AudioUtils;
import electrosphere.controls.ControlHandler;
import electrosphere.entity.CameraEntityUtils;
import electrosphere.entity.types.creature.CreatureUtils;
import electrosphere.renderer.Material;
import electrosphere.renderer.Model;
import electrosphere.renderer.RenderUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.state.movement.GroundMovementTree;
import electrosphere.entity.types.hitbox.HitboxUtils;
import electrosphere.entity.types.item.ItemUtils;
import electrosphere.entity.types.attach.AttachUtils;
import electrosphere.engine.LoadingThread;
import electrosphere.game.client.ClientFunctions;
import electrosphere.game.config.UserSettings;
import electrosphere.game.server.saves.SaveUtils;
import electrosphere.game.server.terrain.manager.ServerTerrainManager;
import electrosphere.game.server.world.MacroData;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.game.simulation.MacroSimulation;
import electrosphere.game.simulation.MicroSimulation;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.RenderingEngine;
import electrosphere.util.FileUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.lwjgl.glfw.*;
import static org.lwjgl.glfw.GLFW.*;
import electrosphere.util.ModelLoader;
import electrosphere.util.worldviewer.TerrainViewer;
import electrosphere.util.Utilities;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.joml.Vector3d;



public class Main {
    
    
    

    
    
    
    //
    //Visualization Controls
    //
    //These are used in calculating the time between frames for visualization (camera) control and such
    public static float deltaTime = 0.0f;
    public static float lastFrame = 0.0f;
    //View Controls
    public static float view_Range = 200000.0f;
    /*
    Mouse Controls
    */
    static float mouse_lastX = 400;
    static float mouse_lastY = 300;
    static double xpos = 400;
    static double ypos = 300;
    static float mouseSensitivity = .1f;
    static double mouse_X_Buffer[] = new double[1];
    static double mouse_Y_Buffer[] = new double[1];
    static float cameraSpeed;
    static float yaw = 150;
    static float pitch = 50;

    
    
    
    static int playerStartRealX = 0;
    static int playerStartRealY = 0;
    
//    public static Camera cam_Player_Orbit;
    //Camera angles using theta-phi system
    //Euler angles where theta is applied, then phi
    //There is no bank applied to the camera
    
    public static Model model;
    
    public static int playerId = -1;
    
    
    public static boolean running = true;
    
    public static Entity letterEntity;
    
    static float targetFrameRate = 1000.0f/144.0f;
    
    public static void main(String args[]){
        
        //
        //
        //     I N I T I A L I Z A T I O N
        //
        //
        
        //load user settings
        UserSettings.loadUserSettings();
        
        //initialize logging interfaces
        LoggerInterface.initLoggers();
        
        //controls
        initControlHandler();
        
        //init global variables
        Globals.initGlobals();
        
        //world gen testing
        //gen terrain
//        Globals.serverTerrainManager = new ServerTerrainManager(2000,50,100,0.0f,0);
//        Globals.serverTerrainManager.load();
//        Globals.serverWorldData = ServerWorldData.createGameWorld(Globals.serverTerrainManager);
//        //gen world
//        Globals.macroData = MacroData.generateWorld(0);
//        Globals.macroData.describeWorld();
//        boolean run = true;
//        Globals.macroSimulation = new MacroSimulation();
//        while(run){
//            Globals.macroSimulation.simulate();
//            try {
//                TimeUnit.MILLISECONDS.sleep(1000);
//            } catch (InterruptedException ex) {
//                ex.printStackTrace();
//            }
//        }

//        if(1==1){
//            Globals.audioEngine = new AudioEngine();
//            Globals.audioEngine.init();
//            Globals.assetManager.addAudioPathToQueue("/Audio/MenuStartup.ogg");
//            Globals.assetManager.loadAssetsInQueue();
//            Globals.audioEngine.setGain(0.1f);
//            AudioSource startupSound = AudioUtils.playAudioAtLocation("/Audio/MenuStartup.ogg", new Vector3f(3,0,0));
//            if(startupSound != null){
//                while(startupSound.isPlaying()){
//                    try {
//                        TimeUnit.MILLISECONDS.sleep(10);
//                    } catch (InterruptedException ex) {
//                        ex.printStackTrace();
//                    }
//                }
//            }
//            System.exit(0);
//        }

        

        //debug: create terrain/world viewer
//        TerrainViewer.runViewer();
        
        //create the drawing context
        Globals.renderingEngine = new RenderingEngine();
        Globals.renderingEngine.createOpenglContext();
        
        //uncomment to test loading a model into engine
//        if(1==1){
//            Globals.assetManager.addModelPathToQueue("/Models/arrow1.fbx");
//            Globals.assetManager.loadAssetsInQueue();
//            Model bowModel = Globals.assetManager.fetchModel("/Models/arrow1.fbx");
//            bowModel.describeHighLevel();
//            System.exit(0);
//        }
        
        //create the audio context
        Globals.audioEngine = new AudioEngine();
        Globals.audioEngine.init();
        Globals.audioEngine.setGain(0.1f);
        
        //init default resources
        Globals.initDefaultGraphicalResources();
        Globals.initDefaultAudioResources();
        
        //fire off a loading thread for the title menus/screen
        Globals.loadingThread = new LoadingThread(LoadingThread.LOAD_TITLE_MENU);
        Globals.loadingThread.start();
        
        //recapture the screen for rendering
        RenderUtils.recaptureScreen();
        
        ///
        ///    C A M E R A     C R E A T I O N
        ///
        Vector3f cameraRotationVector = new Vector3f();
        
        //main loop
        while (running) {
            
            /*
            Frame calculation
            */
            float currentFrame = (float) glfwGetTime();
            deltaTime = currentFrame - lastFrame;
            lastFrame = currentFrame;
            
            
            ///
            ///    A S S E T     M A N A G E R     S T U F F
            ///
            Globals.assetManager.loadAssetsInQueue();
            
            
            
            
            ///
            ///    C L I E N T    N E T W O R K I N G    S T U F F
            ///
            //Why is this its own function? Just to get the networking code out of main()
            if(Globals.RUN_CLIENT && Globals.clientConnection != null){
                Globals.clientConnection.parseMessages();
            }
            
            
            
            ///
            ///    I N P U T     C O N T R O L S
            ///
            cameraSpeed = 2.5f * deltaTime;
//            if (glfwGetKey(Globals.window, GLFW_KEY_ESCAPE) == GLFW_PRESS && Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT) {
//                break;
//            }
            
            //cell tracking values
            Vector3d oldPlayerCharacterPosition = new Vector3d();
            if(Globals.playerCharacter != null){
                oldPlayerCharacterPosition = new Vector3d(EntityUtils.getPosition(Globals.playerCharacter));
            }
            Vector3d newPlayerCharacterPosition = oldPlayerCharacterPosition;
            
            
            //Poll controls
            Globals.controlHandler.pollControls();
            
            
            
            ///
            ///    C L I E N T    S I M U L A T I O N    S T U F F
            ///
            if(Globals.microSimulation != null && Globals.microSimulation.isReady()){
                Globals.microSimulation.simulate();
            }
            ClientFunctions.runClientFunctions();
            
            ///
            ///    M A C R O      S I M U L A T I O N     S T U F F
            ///
            if(Globals.macroSimulation != null && Globals.macroSimulation.isReady()){
                Globals.macroSimulation.simulate();
            }
            
            //
            //      P L A Y E R    W O R L D    P O S I T I O N    U P D A T E
            //
//            if(Globals.playerCharacter != null && Globals.commonWorldData != null){
//                newPlayerCharacterPosition = EntityUtils.getPosition(Globals.playerCharacter);
//                Globals.clientPlayerData.setWorldPosition(Globals.commonWorldData.convertRealToWorld(newPlayerCharacterPosition.x), Globals.commonWorldData.convertRealToWorld(newPlayerCharacterPosition.z));
//            }
            
            ///
            ///      C L I E N T    C E L L    M A N A G E R
            ///
            if(Globals.drawCellManager != null){
                
                //Cell manager do your things
                Globals.drawCellManager.calculateDeltas(oldPlayerCharacterPosition, newPlayerCharacterPosition);
                Globals.drawCellManager.update();
            }
            
            
            
            ///
            ///     C A M E R A      S T U F F
            ///
            //poll mouse variables and update camera variables
            if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT){
                
                updateMouseVariables();
                
                CameraEntityUtils.setCameraPitch(Globals.playerCamera, pitch);
                CameraEntityUtils.setCameraYaw(Globals.playerCamera, yaw);
                
                if(Globals.playerCharacter != null){
                    Vector3d charPos = EntityUtils.getPosition(Globals.playerCharacter);
                    CameraEntityUtils.setCameraCenter(Globals.playerCamera, new Vector3f((float)charPos.x,(float)charPos.y,(float)charPos.z));
                }
                
                float cam_Player_Orbit_Magnitude = 5f;
                cameraRotationVector.x = 0 + (float) Math.cos(yaw / 180.0f * Math.PI) * cam_Player_Orbit_Magnitude;
                cameraRotationVector.y = 0 + (float) Math.sin(pitch / 180.0f * Math.PI) * cam_Player_Orbit_Magnitude;
                cameraRotationVector.z = 0 + (float) Math.sin(yaw / 180.0f * Math.PI) * cam_Player_Orbit_Magnitude;
                cameraRotationVector.normalize();
                
                CameraEntityUtils.setCameraEye(Globals.playerCamera, cameraRotationVector);
                
//                if(Globals.playerCharacter != null){
//                    Vector3f playerPos = EntityUtils.getPosition(Globals.playerCharacter);
//                    if(posX == -1 && playerPos.x > 100){
//                        posX = playerPos.x;
//                        posZ = playerPos.z;
//                    }
//                    posX = posX - (cameraRotationVector.x * 0.01);
//                    posZ = posZ - (cameraRotationVector.z * 0.01);
//                    Vector3f moveVec = new Vector3f(-cameraRotationVector.x,0,-cameraRotationVector.z);
//                    Quaternionf playerRot = new Quaternionf().rotationTo(new Vector3f(0,0,1), moveVec);
//                    EntityUtils.getRotation(Globals.playerCharacter).set(playerRot);
//                    EntityUtils.getPosition(Globals.playerCharacter).set((float)posX,playerPos.y,(float)posZ);
//                }
                
                Globals.viewMatrix = CameraEntityUtils.getCameraViewMatrix(Globals.playerCamera);
            }
            
            
            
            Globals.renderingEngine.drawScreen();
            
            
            if(glfwWindowShouldClose(Globals.window) || Globals.ENGINE_SHUTDOWN_FLAG){
                running = false;
            }
            
            sleep((int)(1000.0*Math.max(0.003, deltaTime-targetFrameRate)));
        }
        //Terminate the program.
        glfwTerminate();
        //used to signal threads to stop
        running = false;
        if(Globals.server != null){
            Globals.server.close();
            Globals.serverThread.interrupt();
        }
        //shut down audio engine
        Globals.audioEngine.shutdown();
    }
    
    static void sleep(int i) {
        try {
            TimeUnit.MILLISECONDS.sleep(i);
        } catch (InterruptedException ex) {
            System.out.println("Sleep somehow interrupted?!");
        }
    }
    
    
    public static boolean isRunning(){
        return running;
    }
    
    
    
    
    public static void initControlHandler(){
        Globals.controlHandler = ControlHandler.generateExampleControlsMap();
//        Globals.controlHandler = FileLoadingUtils.loadModelObjectFromBakedJsonFile("/Config/keybinds.json",ControlHandler.class);
    }
    
    
    
    
    public static void updateMouseVariables(){
        glfwGetCursorPos(Globals.window, mouse_X_Buffer, mouse_Y_Buffer);
        xpos = mouse_X_Buffer[0];
        ypos = mouse_Y_Buffer[0];
        float xoffset = (float) (xpos - mouse_lastX) * mouseSensitivity;
        float yoffset = (float) (mouse_lastY - ypos) * mouseSensitivity;
        mouse_lastX = (float) xpos;
        mouse_lastY = (float) ypos;

        yaw = yaw + xoffset;
        pitch = pitch - yoffset;

        if (pitch > 100.0f) {
            pitch = 100.0f;
        }
        if (pitch < -99.0f) {
            pitch = -99.0f;
        }        
    }
    
    
    
}
