package electrosphere.engine;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.joml.Matrix4f;
import org.joml.Vector3f;

import electrosphere.audio.AudioEngine;
import electrosphere.audio.VirtualAudioSourceManager;
import electrosphere.audio.collision.HitboxAudioService;
import electrosphere.audio.movement.MovementAudioService;
import electrosphere.auth.AuthenticationManager;
import electrosphere.client.fluid.cells.FluidCellManager;
import electrosphere.client.fluid.manager.ClientFluidManager;
import electrosphere.client.foliagemanager.ClientFoliageManager;
import electrosphere.client.player.ClientPlayerData;
import electrosphere.client.scene.ClientSceneWrapper;
import electrosphere.client.scene.ClientWorldData;
import electrosphere.client.sim.ClientSimulation;
import electrosphere.client.terrain.cells.DrawCellManager;
import electrosphere.client.terrain.cells.VoxelTextureAtlas;
import electrosphere.client.terrain.manager.ClientTerrainManager;
import electrosphere.collision.CollisionEngine;
import electrosphere.collision.CollisionWorldData;
import electrosphere.controls.CameraHandler;
import electrosphere.controls.ControlCallback;
import electrosphere.controls.ControlHandler;
import electrosphere.controls.MouseCallback;
import electrosphere.controls.ScrollCallback;
import electrosphere.engine.assetmanager.AssetDataStrings;
import electrosphere.engine.assetmanager.AssetManager;
import electrosphere.engine.loadingthreads.InitialAssetLoading;
import electrosphere.engine.loadingthreads.LoadingThread;
import electrosphere.engine.profiler.Profiler;
import electrosphere.engine.time.Timekeeper;
import electrosphere.entity.Entity;
import electrosphere.entity.Scene;
import electrosphere.game.config.UserSettings;
import electrosphere.game.data.voxel.VoxelType;
import electrosphere.game.server.structure.virtual.StructureManager;
import electrosphere.game.server.world.MacroData;
import electrosphere.logger.LoggerInterface;
import electrosphere.menu.WindowUtils;
import electrosphere.net.client.ClientNetworking;
import electrosphere.net.monitor.NetMonitor;
import electrosphere.net.server.Server;
import electrosphere.net.server.player.Player;
import electrosphere.net.server.player.PlayerManager;
import electrosphere.net.synchronization.client.ClientSynchronizationManager;
import electrosphere.net.synchronization.server.EntityValueTrackingService;
import electrosphere.net.synchronization.server.ServerSynchronizationManager;
import electrosphere.renderer.RenderUtils;
import electrosphere.renderer.RenderingEngine;
import electrosphere.renderer.actor.instance.InstanceManager;
import electrosphere.renderer.light.PointLight;
import electrosphere.renderer.light.SpotLight;
import electrosphere.renderer.loading.ModelPretransforms;
import electrosphere.renderer.meshgen.FluidChunkModelGeneration;
import electrosphere.renderer.model.Material;
import electrosphere.renderer.shader.ShaderOptionMap;
import electrosphere.renderer.shader.ShaderProgram;
import electrosphere.renderer.texture.TextureMap;
import electrosphere.renderer.ui.ElementManager;
import electrosphere.renderer.ui.elements.ImagePanel;
import electrosphere.renderer.ui.font.FontManager;
import electrosphere.script.ScriptEngine;
import electrosphere.server.ai.AIManager;
import electrosphere.server.datacell.EntityDataCellMapper;
import electrosphere.server.datacell.RealmManager;
import electrosphere.server.db.DatabaseController;
import electrosphere.server.pathfinding.NavMeshManager;
import electrosphere.server.saves.Save;
import electrosphere.server.simulation.MacroSimulation;
import electrosphere.server.simulation.MicroSimulation;
import electrosphere.util.FileUtils;

/**
 * Global values
 */
public class Globals {

    //
    //Process data
    //
    public static String javaPID;
    
    //
    //Top level user settings object
    //
    public static UserSettings userSettings;

    //
    //Timekeeper
    //
    public static Timekeeper timekeeper;
    
    //
    //Rendering Engine
    //
    public static RenderingEngine renderingEngine;
    
    //
    //Audio Engine
    //
    public static AudioEngine audioEngine;
    public static VirtualAudioSourceManager virtualAudioSourceManager;
    public static MovementAudioService movementAudioService = new MovementAudioService();
    public static HitboxAudioService hitboxAudioService = new HitboxAudioService();
    
    
    //
    //Core Engine signals
    //
    public static boolean ENGINE_SHUTDOWN_FLAG = false;
    //main debug flag
    //current enables imgui debug menu or not
    public static boolean ENGINE_DEBUG = true;


    //
    //Profiler
    //
    public static Profiler profiler;

    //
    //Garbage Collection
    //
    //set to true to trigger full GC every frame
    //a full GC includes collecting old generations as well -- likely very laggy!!
    public static boolean EXPLICIT_GC = false;
    
    
    //
    //Client connection to server
    //
    public static ClientNetworking clientConnection;
    public static boolean RUN_DEMO = false;
    public static boolean RUN_CLIENT = true;
    public static boolean RUN_HIDDEN = false; //glfw session will be created with hidden window
    public static boolean RUN_AUDIO = true;
    public static int clientCharacterID;
    
    //
    //Server manager thing
    //
    public static Thread serverThread;
    public static Server server;
    public static ServerSynchronizationManager serverSynchronizationManager = new ServerSynchronizationManager();
    public static boolean RUN_SERVER = true;

    //
    //Authentication manager
    //
    public static AuthenticationManager authenticationManager;
    
    
    //
    //Controls Handler
    //
    public static ControlHandler controlHandler;
    public static boolean updateCamera = true;
    public static ControlCallback controlCallback = new ControlCallback();
    public static MouseCallback mouseCallback = new MouseCallback();
    public static ScrollCallback scrollCallback = new ScrollCallback();
    
    
    //
    // Game Save stuff
    //
    public static Save currentSave = null;
    
    
    //
    // Game config
    //
    public static electrosphere.game.data.Config gameConfigDefault;
    public static electrosphere.game.data.Config gameConfigCurrent;
    
    //
    // Database stuff
    //
    public static DatabaseController dbController = new DatabaseController();

    //
    //Camera handler stuff
    //
    public static CameraHandler cameraHandler = new CameraHandler();
    
    //
    //Server scene management
    //
    public static RealmManager realmManager;
    public static EntityDataCellMapper entityDataCellMapper;

    //
    //behavior tree tracking service
    //
    public static EntityValueTrackingService entityValueTrackingService;
    
    //
    //Player manager
    //
    public static PlayerManager playerManager;
    public static Player clientPlayer;
    public static String clientUsername;
    public static String clientPassword;
    
    //
    //Generic OpenGL Statements
    //
    public static long window;
    
    
    
    //
    //OpenGL - Other
    //
    
    public static int WINDOW_WIDTH = 1920;
    public static int WINDOW_HEIGHT = 1080;
    
    //title bar dimensions
    public static int WINDOW_TITLE_BAR_HEIGHT = 0;
    
    public static float verticalFOV = 90;
    public static float aspectRatio = 2.0f;
    
    //matrices for drawing models
    public static Matrix4f viewMatrix = new Matrix4f();
    public static Matrix4f projectionMatrix;
    public static Matrix4f lightDepthMatrix = new Matrix4f();
    
    //locations for shadow map specific variables
    public static int depthMapShaderProgramLoc = 0;
    
    
    
    
    
    
    //
    //OpenGL - Abstracted engine objects
    //
    public static String textureDiffuseDefault;
    public static String textureSpecularDefault;
    public static Material materialDefault;
    
    public static String blackTexture = "Textures/b1.png";
    public static String testingTexture = "Textures/Testing1.png";
    public static String whiteTexture = "Textures/w1.png";
    public static String offWhiteTexture = "Textures/ow1.png";
    
    public static String imagePlaneModelID;
    public static String solidPlaneModelID;
    
    public static ArrayList<PointLight> lightPointListDefault;
    public static SpotLight lightSpotDefault;
    
    public static TextureMap textureMapDefault;
    public static ModelPretransforms modelPretransforms;
    
    public static ShaderProgram defaultMeshShader;
    public static ShaderProgram terrainShaderProgram;
    
    public static String particleBillboardModel;

    public static ShaderOptionMap shaderOptionMap;

    //manages all loaded fonts
    public static FontManager fontManager;
    
    
    
    //
    //Engine - Main managers/variables
    //

    //manages all models loaded into memory
    public static AssetManager assetManager;
    
    //macro simulation
    public static MacroSimulation macroSimulation;
    public static MacroData macroData;

    //flag to only run a simulation without full client
    public static boolean RUN_SIMULATION_ONLY = false;
    
    //micro simulation
    public static MicroSimulation microSimulation;
    
    //script engine
    public static ScriptEngine scriptEngine;
    
    //client scene management
    public static Scene clientScene;
    public static ClientSceneWrapper clientSceneWrapper;
    public static ClientSimulation clientSimulation;
    public static ClientSynchronizationManager clientSynchronizationManager;

    //instanced actor manager
    public static InstanceManager clientInstanceManager = new InstanceManager();

    //client side foliage manager
    public static ClientFoliageManager clientFoliageManager;

    //client world data
    public static ClientWorldData clientWorldData;
    
    //client terrain manager
    public static ClientTerrainManager clientTerrainManager;

    //client fluid manager
    public static ClientFluidManager clientFluidManager;
    
    //client player data
    public static ClientPlayerData clientPlayerData = new ClientPlayerData();
    
    //chunk stuff
    //draw cell manager
    public static DrawCellManager drawCellManager;
    public static VoxelTextureAtlas voxelTextureAtlas = new VoxelTextureAtlas();

    //fluid cell manager
    public static FluidCellManager fluidCellManager;
    
    //navmesh manager
    public static NavMeshManager navMeshManager;
    
    //famous fuckin last words, but temporary solution
    //global arraylist of values for the skybox colors
    //edit(6/1/21):    :upside_down_smile:
    public static ArrayList<Vector3f> skyboxColors;
    
    //thread for loading different game states
    public static List<LoadingThread> loadingThreadsList = new LinkedList<LoadingThread>();
    public static InitialAssetLoading initialAssetLoadingThread = new InitialAssetLoading();

    //manager for all widgets currently being drawn to screen
    public static ElementManager elementManager;
    public static int openInventoriesCount = 0;
    
    //collision world data
    public static CollisionWorldData commonWorldData;
    
    //structure manager
    public static StructureManager structureManager;
    
    
    //the player camera entity
    public static Entity playerCamera;

    //the player in world cursor
    public static Entity playerCursor;
    
    //the creature the player camera will orbit and will receive controlHandler movementTree updates
    public static Entity playerEntity;
    //the entity for the first person modal (view model)
    public static Entity firstPersonEntity;

    //client current selected voxel type
    public static VoxelType clientSelectedVoxelType = null;
    
    //skybox entity
    public static Entity skybox;
    
    
    //ai manager
    public static AIManager aiManager;

    //drag item state
    public static Entity draggedItem = null;
    public static Object dragSourceInventory = null;
    
    
    
    
    
    //
    //Base engine creation flags
    //
    public static boolean HEADLESS = false;
    
    
    //
    //
    // Renderer flags
    //
    //
    public static boolean RENDER_FLAG_RENDER_SHADOW_MAP = false;
    public static boolean RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT = false;
    public static boolean RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER = false;
    public static boolean RENDER_FLAG_RENDER_BLACK_BACKGROUND = true;
    public static boolean RENDER_FLAG_RENDER_WHITE_BACKGROUND = false;
    public static boolean RENDER_FLAG_RENDER_UI = true;
    public static boolean RENDER_FLAG_RENDER_UI_BOUNDS = false;



    //
    // Debugging tools
    //
    public static NetMonitor netMonitor;
    
    
    /**
     * Inits globals
     */
    public static void initGlobals(){
        LoggerInterface.loggerStartup.INFO("Initialize global variables");
        //timekeeper
        timekeeper = new Timekeeper();
        //load in default texture map
        textureMapDefault = TextureMap.construct("Textures/default_texture_map.json");
        //load model pretransforms
        modelPretransforms = FileUtils.loadObjectFromAssetPath("Models/modelPretransforms.json", ModelPretransforms.class);
        modelPretransforms.init();
        //load in shader options map
        shaderOptionMap = FileUtils.loadObjectFromAssetPath("Shaders/shaderoptions.json", ShaderOptionMap.class);
        shaderOptionMap.debug();
        //client scene wrapper
        clientScene = new Scene();
        clientSceneWrapper = new ClientSceneWrapper(clientScene, new CollisionEngine());
        //temporary hold for skybox colors
        skyboxColors = new ArrayList<Vector3f>();
        //load asset manager
        assetManager = new AssetManager();
        //load widget manager
        elementManager = new ElementManager();
        //script engine
        scriptEngine = new ScriptEngine();
        //ai manager
        aiManager = new AIManager();
        //realm & data cell manager
        realmManager = new RealmManager();
        entityDataCellMapper = new EntityDataCellMapper();
        //nav mesh manager
        navMeshManager = new NavMeshManager();
        //terrain
        Globals.clientTerrainManager = new ClientTerrainManager();
        //fluid
        Globals.clientFluidManager = new ClientFluidManager();
        //game config
        gameConfigDefault = electrosphere.game.data.Config.loadDefaultConfig();
        gameConfigCurrent = gameConfigDefault;

        //
        //Values that depend on the loaded config
        Globals.clientSelectedVoxelType = (VoxelType)gameConfigCurrent.getVoxelData().getTypes().toArray()[1];
        //player manager
        playerManager = new PlayerManager();
        //behavior tree tracking service
        entityValueTrackingService = new EntityValueTrackingService();
        //net monitor
        if(Globals.userSettings.getNetRunNetMonitor()){
            netMonitor = new NetMonitor();
        }
        //client synchronization manager
        clientSynchronizationManager = new ClientSynchronizationManager();
        //profiler
        profiler = new Profiler();
    }
    
    /**
     * Inits default audio resources
     */
    public static void initDefaultAudioResources(){
        String[] audioToInit = new String[]{
            "/Audio/inventoryGrabItem.ogg",
            "/Audio/inventorySlotItem.ogg",
            "/Audio/openMenu.ogg",
            "/Audio/closeMenu.ogg",
            "/Audio/ambienceWind1SeamlessMono.ogg",
            "/Audio/weapons/swordUnsheath1.ogg",
            "/Audio/weapons/swoosh-03.ogg",
            "/Audio/movement/Equip A.wav",
            "/Audio/weapons/collisions/Massive Punch A.wav",
            "/Audio/weapons/collisions/Massive Punch B.wav",
            "/Audio/weapons/collisions/Massive Punch C.wav",
            "Audio/weapons/collisions/Sword Hit A.wav",
            "Audio/weapons/collisions/Sword Hit B.wav",
            "Audio/weapons/collisions/Sword Hit C.wav",
            "Audio/weapons/collisions/Sword Hit D.wav",
            "Audio/weapons/collisions/Sword Hit E.wav",
        };
        LoggerInterface.loggerStartup.INFO("Loading default audio resources");
        for(String path : audioToInit){
            Globals.assetManager.addAudioPathToQueue(path);
        }
        Globals.assetManager.loadAssetsInQueue();
        Globals.movementAudioService.init();
    }

    /**
     * Texture paths to be loaded when renderer inits
     */
    private static String[] defaultTexturePaths = new String[]{
        "Textures/default_diffuse.png",
        "Textures/default_specular.png",
        "Textures/b1.png",
        "Textures/w1.png",
        "Textures/ow1.png",
        "Textures/ui/WindowBorder.png",
        "Textures/ui/uiFrame1.png",
        "Textures/ui/uiFrame2.png",
        "Textures/ui/circle.png",
        "Textures/ui/square.png",
        "Textures/color/transparent_green.png",
        "Textures/color/transparent_magenta.png",
        "Textures/color/transparent_orange.png",
        "Textures/color/transparent_teal.png",
        "Textures/color/transparent_yellow.png",
    };
    
    /**
     * Inits default graphical resources
     */
    public static void initDefaultGraphicalResources(){
        LoggerInterface.loggerStartup.INFO("Loading default graphical resources");

        //load default textures
        for(String defaultTexturePath: defaultTexturePaths){
            Globals.assetManager.addTexturePathtoQueue(defaultTexturePath);
        }

        //create default material
        materialDefault = new Material();
        materialDefault.set_diffuse("Textures/default_diffuse.png");
        materialDefault.set_specular("Textures/default_specular.png");

        //create font manager
        fontManager = new FontManager();
        fontManager.loadFonts();
        assetManager.registerModelToSpecificString(RenderUtils.createBitmapCharacter(), AssetDataStrings.BITMAP_CHARACTER_MODEL);
        //particle billboard model
        particleBillboardModel = assetManager.registerModel(RenderUtils.createParticleModel());
        //initialize required windows
        WindowUtils.initBaseWindows();
        //init default shaderProgram
        defaultMeshShader = ShaderProgram.smart_assemble_shader(false,true);
        //init terrain shader program
        terrainShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/terrain2/terrain2.vs", "/Shaders/terrain2/terrain2.fs");
        //init fluid shader program
        FluidChunkModelGeneration.fluidChunkShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/fluid2/fluid2.vs", "/Shaders/fluid2/fluid2.fs");
        //init models
        assetManager.addModelPathToQueue("Models/basic/geometry/unitsphere.glb");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitsphere.fbx");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitsphere_1.fbx");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitsphere_grey.fbx");
        assetManager.addModelPathToQueue("Models/basic/geometry/SmallCube.fbx");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitcylinder.fbx");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitcapsule.glb");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitplane.fbx");
        assetManager.addModelPathToQueue("Models/basic/geometry/unitcube.fbx");
        imagePlaneModelID = assetManager.registerModel(RenderUtils.createPlaneModel("Shaders/plane/plane.vs", "Shaders/plane/plane.fs"));
        assetManager.addShaderToQueue("Shaders/plane/plane.vs", null, "Shaders/plane/plane.fs");
        solidPlaneModelID = assetManager.registerModel(RenderUtils.createInWindowPanel("Shaders/ui/plainBox/plainBox.vs", "Shaders/ui/plainBox/plainBox.fs"));

        //image panel
        ImagePanel.imagePanelModelPath = assetManager.registerModel(RenderUtils.createPlaneModel("Shaders/plane/plane.vs", "Shaders/plane/plane.fs"));

        Globals.assetManager.addShaderToQueue("Shaders/ui/plainBox/plainBox.vs", "Shaders/ui/plainBox/plainBox.fs");
        
        //window content shader
        assetManager.addShaderToQueue("Shaders/ui/windowContent/windowContent.vs", null, "Shaders/ui/windowContent/windowContent.fs");
        
        //debug shaders
        assetManager.addShaderToQueue("Shaders/ui/debug/windowBorder/windowBound.vs", null, "Shaders/ui/debug/windowBorder/windowBound.fs");
        assetManager.addShaderToQueue("Shaders/ui/debug/windowContentBorder/windowContentBound.vs", null, "Shaders/ui/debug/windowContentBorder/windowContentBound.fs");

        //as these assets are required for the renderer to work, we go ahead and
        //load them into memory now. The loading time penalty is worth it I think.
        Globals.assetManager.loadAssetsInQueue();
    }

    

}
