package net.halbear.Terrain4J.EngineCore.Main;

import net.halbear.Terrain4J.EngineCore.Display.Window;
import net.halbear.Terrain4J.EngineCore.Logic.*;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext;
import net.halbear.Terrain4J.EngineCore.Main.Scene.Scene;
import net.halbear.Terrain4J.EngineCore.Profiling.CPUMonitor;
import net.halbear.Terrain4J.EngineCore.Threads.FastTickThread;
import net.halbear.Terrain4J.EngineCore.Threads.MainThread;
import net.halbear.Terrain4J.EngineCore.Threads.RenderThread;
import org.tinylog.Logger;

public class PrimaryRuntime {
    private int Frames = 0;
    private int TickFrames = 0;
    private double TickRate = 0;
    private static MainThread PrimaryThread;
    private static RenderThread DrawingThread;
    private static FastTickThread FastThread;
    private static boolean running = true;
    private static Window window;
    private static EngineInstance engineInstance;
    public static int ThreadsOpen = 0;
    public static boolean CanClose = false;
    public static long ShutDownStart;
    private static long FrameAccuracy;
    private static long OriginalFrameAccuracy;
    private static boolean CappedToTickRate = false;
    public final CPUMonitor cpuProfiler;
    private static GameLogic gameLogic;

    public PrimaryRuntime(String windowTitle, GameLogic appLogic) {
        window = new Window(windowTitle);
        gameLogic = appLogic;
        engineInstance = new EngineInstance(window, new Scene(window));
        InitData initData = appLogic.Initialise(engineInstance);
        PrimaryThread = new MainThread(engineInstance, appLogic);
        FastThread = new FastTickThread(engineInstance, appLogic);
        DrawingThread = new RenderThread(window, engineInstance, initData, appLogic);
        cpuProfiler = new CPUMonitor();
        Thread.currentThread().setName("Terrain4J Primary Runtime Thread");
    }
    public static void UpdateFrameAccuracy(){
        if(EngineConfig.getInstance().IsEngineThrottled()){
            Logger.debug("ThrottlingEngine");
            FrameAccuracy = EngineConfig.getInstance().GetThrottleAccuracy();
        } else{
            Logger.debug("UnThrottlingEngine");
            FrameAccuracy= OriginalFrameAccuracy;
        }
        PrimaryThread.UpdateFrameAccuracy();
        DrawingThread.UpdateFrameAccuracy();
        FastThread.UpdateFrameAccuracy();
    }
    public void UpdateTickRateThreshold(){
        CappedToTickRate = EngineConfig.getInstance().GetPrimaryTickCap();
        if (CappedToTickRate){
            TickRate = (double) 1000000000 / (double) EngineConfig.getInstance().getTickRate();
        } else{
            TickRate = 0;
        }
    }

    public void PrimaryRuntimeInputUpdate(long nsTimeDiff){
        gameLogic.CameraInput(PrimaryRuntime.GetEngineInstance(), (nsTimeDiff));
    }

    public void run(){
        UpdateTickRateThreshold();
        long InitialTime = System.nanoTime();
        long updateTime = InitialTime;
        double FrameDiff =0;
        double TickDiff =0;
        double SecondCounter =0;
        double cycleDiff = 0;
        PrimaryThread.StartThread();
        DrawingThread.StartThread();
        FastThread.StartThread();
        FrameAccuracy = EngineConfig.getInstance().getAccuracy();
        OriginalFrameAccuracy = FrameAccuracy;
        Logger.debug("Primary Thread Started");
        while (!window.shouldClose() && running){
            long now = System.nanoTime();
            cycleDiff = now - InitialTime;
            FrameDiff += cycleDiff;
            TickDiff += cycleDiff;
            SecondCounter += cycleDiff;
            if (FrameDiff >= FrameAccuracy){
                window.pollEvents();
                Frames++;
                if (TickDiff >= TickRate) {
                    TickFrames++;
                    long nsTimeDiff = now - updateTime;
                    PrimaryRuntimeInputUpdate(nsTimeDiff);
                    updateTime = now;
                    TickDiff = 0;
                }
                if (SecondCounter >= 1000000000L){
                    SecondCounter = 0;
                    EngineConfig.FrameRate_WINDOW = EngineConfig.RENDERFRAMES;
                    EngineConfig.FrameRate_PRIMARYTHREAD = Frames;
                    EngineConfig.TPS = TickFrames;
                    Logger.info("Primary Thread FPS: " + Frames + "   TPS: " + TickFrames + "   Window Draw FPS: " + EngineConfig.RENDERFRAMES);
                    Logger.info(cpuProfiler.LogCPUUsage() + "\t");
                    Frames = 0;
                    TickFrames = 0;
                    EngineConfig.RENDERFRAMES = 0;
                }
                FrameDiff = 0;
            }
            InitialTime = now;
        }
        ShutDownStart = System.nanoTime();
        Logger.debug("Closing Software of name [{}]",EngineConfig.getInstance().GetSoftwareName());
        PrimaryThread.KillThread();
        DrawingThread.KillThread();
        cleanup();
    }


    public static EngineInstance GetEngineInstance(){
        return engineInstance;
    }
    public static void CloseRuntime(){
        running = false;
    }
    private void cleanup() {
        engineInstance.cleanup();
        window.cleanup();
        Logger.debug("Process still running: [{}]",Thread.getAllStackTraces().keySet().toString());
        if (ThreadsOpen > 0 || VulkanContext.VulkanLayersOpen > -1) {
            Logger.debug("Waiting to close Primary Thread, making sure vulkan has shut down properly first and Engine Threads are closed\nThreads Open:[{}]\nVulkan Layers Open:[{}]", ThreadsOpen, VulkanContext.VulkanLayersOpen < 0 ? "All processes closed and vulkan instance removed" : VulkanContext.VulkanLayersOpen);
            while ((ThreadsOpen > 0 || VulkanContext.VulkanLayersOpen > 0) && VulkanContext.VulkanLayersOpen > -1 && !CanClose) {
                if (!DrawingThread.FetchThread().isAlive() && !PrimaryThread.FetchThread().isAlive() && ThreadsOpen >0){
                    break;
                }
            }
        }
        Logger.debug("Closing Primary Thread, Thread: [{}]",Thread.currentThread().toString());
        double ShutDownTime = Math.floor(((double)(System.nanoTime() - ShutDownStart)/1000000.0)*1000.0)/1000.0;
        Logger.info("Successful stable Shut Down of Terrain4J in [{}] MilliSeconds", ShutDownTime);
    }
}
