package com.openosrs.client.engine;

import com.openosrs.client.core.ClientCore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * GameEngine - Core game engine managing the main game loop and subsystems.
 * 
 * This modernized engine replaces the obfuscated GameEngine from the original client.
 * It handles:
 * - Main game loop timing (50 FPS)
 * - Rendering coordination
 * - Input processing
 * - Network message processing
 * - Game state updates
 * 
 * The engine is designed to be efficient and agent-friendly, providing precise
 * timing and clear interfaces for automated gameplay.
 */
public class GameEngine {
    private static final Logger logger = LoggerFactory.getLogger(GameEngine.class);
    
    // Game loop timing (50 FPS = 20ms per tick)
    private static final int TARGET_FPS = 50;
    private static final long TICK_DURATION_MS = 1000 / TARGET_FPS;
    
    private final ClientCore clientCore;
    private final ScheduledExecutorService gameLoopExecutor;
    private final AtomicBoolean running = new AtomicBoolean(false);
    private final AtomicLong tickCount = new AtomicLong(0);
    
    // Engine subsystems
    private final RenderingEngine renderingEngine;
    private final NetworkEngine networkEngine;
    private final InputEngine inputEngine;
    private final PhysicsEngine physicsEngine;
    
    // Performance tracking
    private final AtomicLong lastTickTime = new AtomicLong(0);
    private final AtomicLong avgTickTime = new AtomicLong(0);
    
    public GameEngine(ClientCore clientCore) {
        this.clientCore = clientCore;
        this.gameLoopExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread t = new Thread(r, "Game-Loop");
            t.setDaemon(true);
            return t;
        });
        
        // Initialize subsystems
        this.renderingEngine = new RenderingEngine(clientCore);
        this.networkEngine = new NetworkEngine(clientCore);
        this.inputEngine = new InputEngine(clientCore);
        this.physicsEngine = new PhysicsEngine(clientCore);
        
        logger.debug("GameEngine created with subsystems");
    }
    
    /**
     * Initialize the game engine and all subsystems.
     */
    public void initialize() {
        logger.info("Initializing GameEngine");
        
        try {
            // Initialize subsystems in order
            renderingEngine.initialize();
            networkEngine.initialize();
            inputEngine.initialize();
            physicsEngine.initialize();
            
            logger.info("GameEngine subsystems initialized");
            
        } catch (Exception e) {
            logger.error("Failed to initialize GameEngine", e);
            throw new RuntimeException("GameEngine initialization failed", e);
        }
    }
    
    /**
     * Start the main game loop.
     */
    public void startGameLoop() {
        if (running.get()) {
            logger.warn("Game loop already running");
            return;
        }
        
        logger.info("Starting game loop at {} FPS", TARGET_FPS);
        running.set(true);
        
        // Schedule the game loop to run at fixed rate
        gameLoopExecutor.scheduleAtFixedRate(
            this::gameLoop, 
            0, 
            TICK_DURATION_MS, 
            TimeUnit.MILLISECONDS
        );
    }
    
    /**
     * Stop the game loop and shutdown the engine.
     */
    public void shutdown() {
        if (!running.get()) {
            logger.warn("Game engine not running");
            return;
        }
        
        logger.info("Shutting down GameEngine");
        running.set(false);
        
        try {
            // Shutdown game loop
            gameLoopExecutor.shutdown();
            if (!gameLoopExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                gameLoopExecutor.shutdownNow();
            }
            
            // Shutdown subsystems in reverse order
            physicsEngine.shutdown();
            inputEngine.shutdown();
            networkEngine.shutdown();
            renderingEngine.shutdown();
            
            logger.info("GameEngine shutdown complete");
            
        } catch (Exception e) {
            logger.error("Error during GameEngine shutdown", e);
        }
    }
    
    /**
     * Main game loop - called every tick (20ms).
     */
    private void gameLoop() {
        if (!running.get()) {
            return;
        }
        
        long startTime = System.nanoTime();
        
        try {
            // Increment tick counter
            long currentTick = tickCount.incrementAndGet();
            
            // Process network messages first
            networkEngine.processMessages();
            
            // Process input
            inputEngine.processInput();
            
            // Update physics and game state
            physicsEngine.update();
            
            // Update client core (game state)
            clientCore.tick();
            
            // Render frame
            renderingEngine.render();
            
            // Track performance
            updatePerformanceMetrics(startTime);
            
            // Log every 1000 ticks (20 seconds)
            if (currentTick % 1000 == 0) {
                logger.debug("Game loop tick {}, avg time: {}ms", 
                    currentTick, avgTickTime.get() / 1_000_000);
            }
            
        } catch (Exception e) {
            logger.error("Error in game loop tick {}", tickCount.get(), e);
            
            // Don't let a single tick error crash the game
            // Log it and continue
        }
    }
    
    private void updatePerformanceMetrics(long startTime) {
        long endTime = System.nanoTime();
        long tickTime = endTime - startTime;
        
        lastTickTime.set(tickTime);
        
        // Calculate rolling average (simple exponential moving average)
        long currentAvg = avgTickTime.get();
        long newAvg = currentAvg == 0 ? tickTime : (currentAvg * 9 + tickTime) / 10;
        avgTickTime.set(newAvg);
        
        // Warn if tick is taking too long
        long tickTimeMs = tickTime / 1_000_000;
        if (tickTimeMs > TICK_DURATION_MS * 2) {
            logger.warn("Slow game tick: {}ms (target: {}ms)", tickTimeMs, TICK_DURATION_MS);
        }
    }
    
    /**
     * Check if the game engine is running.
     */
    public boolean isRunning() {
        return running.get();
    }
    
    /**
     * Get the current tick count.
     */
    public long getTickCount() {
        return tickCount.get();
    }
    
    /**
     * Get the last tick execution time in nanoseconds.
     */
    public long getLastTickTime() {
        return lastTickTime.get();
    }
    
    /**
     * Get the average tick execution time in nanoseconds.
     */
    public long getAverageTickTime() {
        return avgTickTime.get();
    }
    
    /**
     * Get the current FPS based on tick timing.
     */
    public double getCurrentFPS() {
        long avgTime = avgTickTime.get();
        if (avgTime == 0) return 0;
        return 1_000_000_000.0 / avgTime;
    }
    
    // Accessors for subsystems
    public RenderingEngine getRenderingEngine() { return renderingEngine; }
    public NetworkEngine getNetworkEngine() { return networkEngine; }
    public InputEngine getInputEngine() { return inputEngine; }
    public PhysicsEngine getPhysicsEngine() { return physicsEngine; }
    
    /**
     * Force a single game tick (useful for testing).
     */
    public void forceTick() {
        if (running.get()) {
            logger.warn("Cannot force tick while game loop is running");
            return;
        }
        
        gameLoop();
    }
    
    /**
     * Get detailed engine statistics.
     */
    public EngineStats getStats() {
        return new EngineStats(
            tickCount.get(),
            lastTickTime.get() / 1_000_000, // Convert to ms
            avgTickTime.get() / 1_000_000,   // Convert to ms
            getCurrentFPS(),
            running.get()
        );
    }
    
    /**
     * Engine statistics data class.
     */
    public static class EngineStats {
        private final long tickCount;
        private final long lastTickTimeMs;
        private final long avgTickTimeMs;
        private final double currentFPS;
        private final boolean running;
        
        public EngineStats(long tickCount, long lastTickTimeMs, long avgTickTimeMs, 
                          double currentFPS, boolean running) {
            this.tickCount = tickCount;
            this.lastTickTimeMs = lastTickTimeMs;
            this.avgTickTimeMs = avgTickTimeMs;
            this.currentFPS = currentFPS;
            this.running = running;
        }
        
        public long getTickCount() { return tickCount; }
        public long getLastTickTimeMs() { return lastTickTimeMs; }
        public long getAvgTickTimeMs() { return avgTickTimeMs; }
        public double getCurrentFPS() { return currentFPS; }
        public boolean isRunning() { return running; }
        
        @Override
        public String toString() {
            return String.format("EngineStats{ticks=%d, lastTick=%dms, avgTick=%dms, fps=%.1f, running=%s}",
                tickCount, lastTickTimeMs, avgTickTimeMs, currentFPS, running);
        }
    }
}