package com.livingworld.bootstrap;

import com.livingworld.commands.LivingWorldCommandRoot;
import com.livingworld.config.DefaultConfigService;
import com.livingworld.config.SimulationConfig;
import com.livingworld.core.LivingWorldConstants;
import com.livingworld.core.services.CoreServices;
import com.livingworld.core.services.FileRegionPersistenceService;
import com.livingworld.core.services.ServiceRegistry;
import com.livingworld.core.simulation.DefaultTimeService;
import com.livingworld.core.simulation.SimulationManager;
import com.livingworld.core.simulation.SimulationScheduler;
import com.livingworld.debug.SimulationProfiler;
import com.livingworld.debug.DiagnosticCategory;
import com.livingworld.debug.LivingWorldLogger;
import com.livingworld.events.LivingWorldEventBus;
import com.livingworld.modules.ModuleContext;
import com.livingworld.modules.ModuleRegistry;
import com.livingworld.modules.ServerContext;
import com.livingworld.platform.PlatformAdapter;
import com.livingworld.regions.RegionFactory;
import com.livingworld.regions.RegionLifecycleController;
import com.livingworld.regions.RegionManager;
import com.livingworld.regions.RegionStorage;
import com.livingworld.regions.cache.RegionCache;
import com.livingworld.regions.query.RegionQueryEngine;
import com.mojang.brigadier.CommandDispatcher;
import java.nio.file.Path;
import net.minecraft.commands.CommandSourceStack;

/**
 * Bootstrap class for Living World mod startup lifecycle.
 */
public final class LivingWorldBootstrap {

    private PlatformAdapter platformAdapter;
    private Path worldSaveDirectory;
    private ServiceRegistry services;
    private RegionManager regionManager;
    private ModuleRegistry moduleRegistry;
    private SimulationManager simulationManager;
    private boolean initialized;
    private boolean serverReady;

    /**
     * Called once during mod construction.
     */
    public void initialize(PlatformAdapter platformAdapter) {
        if (initialized) {
            throw new IllegalStateException("bootstrap is already initialized");
        }
        if (platformAdapter == null) {
            throw new IllegalArgumentException("platformAdapter must not be null");
        }
        this.platformAdapter = platformAdapter;
        this.platformAdapter.registerCommands();
        this.platformAdapter.registerServerTickHook();
        this.platformAdapter.registerPlayerEventHooks();
        this.initialized = true;

        LivingWorldLogger.info(
                DiagnosticCategory.BOOTSTRAP,
                "Platform=" + platformAdapter.getPlatformName()
                        + ", minecraft=" + platformAdapter.getMinecraftVersion()
                        + ", loader=" + platformAdapter.getLoaderVersion()
                        + ", dedicatedServer=" + platformAdapter.isDedicatedServer());
        LivingWorldLogger.info(
                DiagnosticCategory.BOOTSTRAP,
                "initialize - mod bootstrap initialized.");
    }

    /**
     * Called during common (dedicated & integrated server) setup phase.
     */
    public void onCommonSetup() {
        requireInitialized();
        LivingWorldLogger.info(
                DiagnosticCategory.BOOTSTRAP,
                "onCommonSetup - common setup phase reached.");
    }

    /**
     * Called when the server is starting.
     */
    public void onServerStarting(Path worldSaveDirectory) {
        requireInitialized();
        if (worldSaveDirectory == null) {
            throw new IllegalArgumentException("worldSaveDirectory must not be null");
        }
        if (serverReady) {
            throw new IllegalStateException("server services are already initialized");
        }
        this.worldSaveDirectory = worldSaveDirectory;
        createServerServices(worldSaveDirectory);
        this.serverReady = true;
        LivingWorldLogger.info(
                DiagnosticCategory.BOOTSTRAP,
                "onServerStarting - services ready at " + worldSaveDirectory);
    }

    /**
     * Called when the server has finished starting.
     */
    public void onServerStarted() {
        requireServerReady();
        ServerContext context = new ServerContext();
        moduleRegistry.getEnabledModules().forEach(module -> module.onServerStarted(context));
        LivingWorldLogger.info(
                DiagnosticCategory.BOOTSTRAP,
                "onServerStarted - server started.");
    }

    /**
     * Called when the server is stopping.
     */
    public void onServerStopping() {
        if (!serverReady) {
            return;
        }
        regionManager.flushAll();
        moduleRegistry.shutdownAll();
        serverReady = false;
        LivingWorldLogger.info(
                DiagnosticCategory.BOOTSTRAP,
                "onServerStopping - persistence flushed and modules stopped.");
    }

    public void onServerTick() {
        if (!serverReady) {
            return;
        }
        long previousSimulationTick = simulationManager.getSimulationTickCounter();
        simulationManager.onMinecraftServerTick();
        if (simulationManager.getSimulationTickCounter() != previousSimulationTick) {
            regionManager.saveDirtyRegions();
        }
    }

    public void registerCommands(CommandDispatcher<CommandSourceStack> dispatcher) {
        requireInitialized();
        LivingWorldCommandRoot.registerDeferred(
                dispatcher,
                () -> requireService(regionManager, "regionManager"),
                () -> requireService(moduleRegistry, "moduleRegistry"),
                () -> requireService(simulationManager, "simulationManager"));
    }

    public Path getWorldSaveDirectory() {
        return worldSaveDirectory;
    }

    public boolean isServerReady() {
        return serverReady;
    }

    public ServiceRegistry getServices() {
        requireServerReady();
        return services;
    }

    private void createServerServices(Path saveDirectory) {
        DefaultConfigService configService = new DefaultConfigService();
        SimulationConfig config = configService.getSimulationConfig();
        FileRegionPersistenceService persistenceService =
                new FileRegionPersistenceService(
                        saveDirectory.resolve("living_world"),
                        LivingWorldConstants.MOD_VERSION);
        RegionCache regionCache = new RegionCache();
        RegionQueryEngine queryEngine = new RegionQueryEngine(regionCache);
        regionManager = new RegionManager(
                new RegionFactory(),
                new RegionStorage(persistenceService),
                regionCache,
                queryEngine,
                new RegionLifecycleController(),
                config);
        moduleRegistry = new ModuleRegistry();
        LivingWorldEventBus eventBus = new LivingWorldEventBus();
        DefaultTimeService timeService = new DefaultTimeService();
        SimulationScheduler scheduler = new SimulationScheduler(config);
        SimulationProfiler profiler = new SimulationProfiler();
        simulationManager = new SimulationManager(
                scheduler,
                regionManager,
                moduleRegistry,
                eventBus,
                timeService,
                persistenceService,
                profiler);

        services = new ServiceRegistry();
        services.register(CoreServices.CONFIG, configService);
        services.register(CoreServices.REGIONS, regionManager);
        services.register(CoreServices.SIMULATION, simulationManager);
        services.register(CoreServices.PERSISTENCE, persistenceService);
        services.register(CoreServices.EVENTS, eventBus);
        services.register(CoreServices.MODULES, moduleRegistry);
        services.register(CoreServices.TIME, timeService);
        services.register(CoreServices.DEBUG, profiler);
        services.lock();

        moduleRegistry.initializeAll(new ModuleContext(services));
    }

    private void requireInitialized() {
        if (!initialized) {
            throw new IllegalStateException("bootstrap has not been initialized");
        }
    }

    private void requireServerReady() {
        requireInitialized();
        if (!serverReady) {
            throw new IllegalStateException("server services are not ready");
        }
    }

    private static <T> T requireService(T service, String name) {
        if (service == null) {
            throw new IllegalStateException(name + " is not ready");
        }
        return service;
    }
}
