late-java-core 2.2.9

A Rust library for launching Minecraft Java Edition
use super::*;
use crate::error::{Result, LateJavaCoreError};
use crate::minecraft::{
    VersionManifest, VersionInfo, MinecraftArguments, MinecraftAssets, 
    MinecraftLibraries, JavaDownloader, MinecraftBundle, MinecraftLoader
};
use crate::downloader::Downloader;
use crate::utils::is_old;
use std::path::Path;
use std::process::{Command, Stdio};
use tokio::process::Command as AsyncCommand;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::sync::broadcast;

/// Launcher principal de Minecraft
pub struct Launch {
    downloader: Arc<Mutex<Downloader>>,
    event_sender: broadcast::Sender<LaunchEvent>,
}

/// Eventos del launcher
#[derive(Debug, Clone)]
pub enum LaunchEvent {
    Progress { downloaded: u64, total: u64, element: Option<String> },
    Speed(f64),
    Estimated(f64),
    Extract(String),
    Check { progress: u64, size: u64, element: Option<String> },
    Patch(String),
    Data(String),
    Close(String),
    Error(String),
}

impl Launch {
    /// Crear un nuevo launcher
    pub fn new() -> Self {
        let (event_sender, _) = broadcast::channel(100);
        Self {
            downloader: Arc::new(Mutex::new(Downloader::new())),
            event_sender,
        }
    }

    /// Obtener el receptor de eventos
    pub fn subscribe(&self) -> broadcast::Receiver<LaunchEvent> {
        self.event_sender.subscribe()
    }

    /// Emitir evento
    fn emit(&self, event: LaunchEvent) {
        let _ = self.event_sender.send(event);
    }

    /// Lanzar Minecraft con las opciones especificadas
    pub async fn launch(&mut self, options: LaunchOptions) -> Result<()> {
        // Validar opciones
        self.validate_options(&options)?;

        // Descargar información de versión
        let version_info = self.get_version_info(&options).await?;
        
        // Descargar archivos del juego
        let download_result = self.download_game(&options, &version_info).await?;
        let (minecraft_json, minecraft_loader, minecraft_version, minecraft_java) = download_result;
        
        // Construir argumentos de lanzamiento
        let minecraft_arguments = self.get_minecraft_arguments(&options, &minecraft_json, minecraft_loader.as_ref()).await?;
        let loader_arguments = self.get_loader_arguments(&options, minecraft_loader.as_ref(), &minecraft_version).await?;
        
        // Combinar argumentos
        let mut launch_args = Vec::new();
        launch_args.extend(minecraft_arguments.jvm);
        launch_args.extend(minecraft_arguments.classpath);
        launch_args.extend(loader_arguments.jvm);
        if let Some(main_class) = minecraft_arguments.main_class {
            launch_args.push(main_class);
        }
        launch_args.extend(minecraft_arguments.game);
        launch_args.extend(loader_arguments.game);
        
        // Lanzar el juego
        self.execute_minecraft(&options, launch_args, &minecraft_java).await?;

        Ok(())
    }

    fn validate_options(&self, options: &LaunchOptions) -> Result<()> {
        if options.authenticator.access_token.is_empty() {
            return Err(LateJavaCoreError::Validation("Authenticator is required".to_string()));
        }

        if options.path.is_empty() {
            return Err(LateJavaCoreError::Validation("Path is required".to_string()));
        }

        if options.version.is_empty() {
            return Err(LateJavaCoreError::Validation("Version is required".to_string()));
        }

        if options.memory.min.is_empty() || options.memory.max.is_empty() {
            return Err(LateJavaCoreError::Validation("Memory options are required".to_string()));
        }

        // Validar límites de descarga concurrente
        if let Some(limit) = options.download_file_multiple {
            if limit < 1 {
                return Err(LateJavaCoreError::Validation("downloadFileMultiple must be at least 1".to_string()));
            }
            if limit > 30 {
                return Err(LateJavaCoreError::Validation("downloadFileMultiple cannot exceed 30".to_string()));
            }
        }

        Ok(())
    }

    /// Descargar el juego completo
    async fn download_game(&mut self, options: &LaunchOptions, version_info: &VersionInfo) -> Result<(serde_json::Value, Option<serde_json::Value>, String, JavaDownloadResult)> {
        // Obtener información de versión
        let info_version = self.get_info_version(options).await?;
        let (json, version) = info_version;
        let mut loader_json: Option<serde_json::Value> = None;

        // Crear instancias de los módulos
        let libraries = MinecraftLibraries::new(crate::minecraft::libraries::LibrariesOptions {
            path: options.path.clone(),
            instance: options.instance.clone(),
            headers: options.headers.clone(),
            token: options.token.clone(),
        });

        let bundle = MinecraftBundle::new(crate::minecraft::bundle::MinecraftBundleOptions {
            path: options.path.clone(),
            instance: options.instance.clone(),
            ignored: options.ignored.clone(),
        });

        let java = JavaDownloader::new(crate::minecraft::java::JavaDownloaderOptions {
            path: options.path.clone(),
            java: crate::minecraft::java::JavaOptions {
                version: options.java.version.clone(),
                java_type: options.java.java_type.clone(),
            },
            intel_enabled_mac: Some(false),
        });

        // Obtener librerías del juego
        let game_libraries = libraries.get_libraries(json.clone()).await?;
        let game_assets_other = libraries.get_assets_others(options.url.as_deref()).await?;
        
        let mut assets = MinecraftAssets::new(crate::minecraft::assets::MinecraftAssetsOptions {
            path: options.path.clone(),
            instance: options.instance.clone(),
        });
        let game_assets = assets.get_assets(&json).await?;
        
        let game_java = if options.java.path.is_some() {
            crate::minecraft::java::JavaDownloadResult {
                files: Vec::new(),
                path: options.java.path.as_ref().unwrap().clone(),
                error: None,
                message: None,
            }
        } else {
            java.get_java_files(&json).await?
        };

        if let Some(true) = game_java.error {
            return Err(LateJavaCoreError::Java("Java download failed".to_string()));
        }

        // Verificar archivos que necesitan descarga
        let mut all_files = Vec::new();
        all_files.extend(game_libraries);
        all_files.extend(game_assets_other);
        all_files.extend(game_assets);
        all_files.extend(game_java.files);

        let files_list = bundle.check_bundle(&mut all_files).await?;

        if !files_list.is_empty() {
            // Preparar headers para el descargador
            let mut headers = options.headers.clone();
            if options.token.is_some() && headers.is_none() {
                let mut auth_headers = std::collections::HashMap::new();
                auth_headers.insert("Authorization".to_string(), options.token.as_ref().unwrap().clone());
                headers = Some(auth_headers);
            }

            let downloader = if let Some(headers) = headers {
                Downloader::with_headers(headers)
            } else {
                Downloader::new()
            };

            let total_size = bundle.get_total_size(&files_list).await?;

            // Convertir a formato de descarga
            let download_options: Vec<crate::downloader::DownloadOptions> = files_list.into_iter().map(|file| {
                crate::downloader::DownloadOptions {
                    url: file.url.unwrap_or_default(),
                    path: file.path.clone(),
                    length: file.size,
                    folder: file.folder.unwrap_or_default(),
                    r#type: file.r#type,
                }
            }).collect();

            // Descargar archivos
            downloader.download_file_multiple(
                &download_options,
                total_size,
                options.download_file_multiple.unwrap_or(5),
                options.timeout.unwrap_or(10000),
            ).await?;
        }

        // Instalar loader si está habilitado
        if options.loader.enable {
            let loader_install = MinecraftLoader::new(crate::minecraft::loader::LoaderOptions {
                path: options.path.clone(),
                loader: crate::minecraft::loader::LoaderConfig {
                    path: options.loader.path.clone(),
                    loader_type: options.loader.loader_type.clone(),
                    build: options.loader.build.clone(),
                },
                download_file_multiple: options.download_file_multiple,
            });

            let java_path = if options.java.path.is_some() {
                options.java.path.as_ref().unwrap().clone()
            } else {
                game_java.path.clone()
            };

            match loader_install.get_loader(&version, &java_path).await {
                Ok(json_loader) => {
                    loader_json = Some(serde_json::to_value(json_loader)?);
                }
                Err(err) => {
                    return Err(err);
                }
            }
        }

        // Verificar archivos si está habilitado
        if options.verify {
            let mut verify_files = Vec::new();
            verify_files.extend(game_assets_other);
            verify_files.extend(game_assets);
            verify_files.extend(game_java.files);
            bundle.check_files(&verify_files).await?;
        }

        // Extraer nativos
        let natives = libraries.natives(&game_libraries).await?;
        if natives.is_empty() {
            // json.nativesList = false; // Esto se manejaría en el JSON
        }

        // Copiar assets legacy si es necesario
        if is_old(&json) {
            assets.copy_assets(&json)?;
        }

        Ok((json, loader_json, version, game_java))
    }

    /// Obtener información de versión
    async fn get_info_version(&self, options: &LaunchOptions) -> Result<(serde_json::Value, String)> {
        let manifest = VersionManifest::fetch().await?;
        let version = self.resolve_version(&manifest, &options.version)?;
        
        // Descargar JSON de la versión
        let response = reqwest::get(&version.url).await?;
        let json: serde_json::Value = response.json().await?;
        
        Ok((json, version.id))
    }

    /// Obtener argumentos de Minecraft
    async fn get_minecraft_arguments(&self, options: &LaunchOptions, json: &serde_json::Value, loader_json: Option<&serde_json::Value>) -> Result<crate::minecraft::arguments::LaunchArguments> {
        let arguments = MinecraftArguments::new(options.clone(), options.authenticator.clone());
        arguments.get_arguments(json, loader_json).await
    }

    /// Obtener argumentos del loader
    async fn get_loader_arguments(&self, options: &LaunchOptions, loader_json: Option<&serde_json::Value>, version: &str) -> Result<crate::minecraft::loader::LoaderArgumentsResult> {
        if let Some(loader) = loader_json {
            let loader_install = MinecraftLoader::new(crate::minecraft::loader::LoaderOptions {
                path: options.path.clone(),
                loader: crate::minecraft::loader::LoaderConfig {
                    path: options.loader.path.clone(),
                    loader_type: options.loader.loader_type.clone(),
                    build: options.loader.build.clone(),
                },
                download_file_multiple: options.download_file_multiple,
            });
            
            let loader_data: crate::minecraft::loader::LoaderJson = serde_json::from_value(loader.clone())?;
            loader_install.get_arguments(Some(&loader_data), version).await
        } else {
            Ok(crate::minecraft::loader::LoaderArgumentsResult {
                game: Vec::new(),
                jvm: Vec::new(),
                main_class: None,
            })
        }
    }

    async fn get_version_info(&self, options: &LaunchOptions) -> Result<VersionInfo> {
        let manifest = VersionManifest::fetch().await?;
        let version = self.resolve_version(&manifest, &options.version)?;
        Ok(version)
    }

    fn resolve_version(&self, manifest: &VersionManifest, version: &str) -> Result<VersionInfo> {
        let resolved_version = match version {
            "latest_release" | "r" | "lr" => &manifest.latest.release,
            "latest_snapshot" | "s" | "ls" => &manifest.latest.snapshot,
            _ => version,
        };

        manifest.versions
            .iter()
            .find(|v| v.id == resolved_version)
            .cloned()
            .ok_or_else(|| LateJavaCoreError::Minecraft(format!("Version {} not found", version)))
    }

    /// Ejecutar Minecraft
    async fn execute_minecraft(&self, options: &LaunchOptions, args: Vec<String>, java_result: &crate::minecraft::java::JavaDownloadResult) -> Result<()> {
        let java_path = if options.java.path.is_some() {
            options.java.path.as_ref().unwrap()
        } else {
            &java_result.path
        };

        let working_dir = if let Some(instance) = &options.instance {
            Path::new(&options.path).join("instances").join(instance)
        } else {
            Path::new(&options.path).to_path_buf()
        };

        // Crear directorio de trabajo si no existe
        if !working_dir.exists() {
            std::fs::create_dir_all(&working_dir)?;
        }

        // Crear logs de argumentos (ocultando información sensible)
        let mut arguments_logs = args.join(" ");
        arguments_logs = arguments_logs.replace(&options.authenticator.access_token, "????????");
        arguments_logs = arguments_logs.replace(&options.authenticator.client_token, "????????");
        arguments_logs = arguments_logs.replace(&options.authenticator.uuid, "????????");
        if let Some(xbox_account) = &options.authenticator.xbox_account {
            arguments_logs = arguments_logs.replace(&xbox_account.xuid, "????????");
        }
        arguments_logs = arguments_logs.replace(&format!("{}/", options.path), "");

        self.emit(LaunchEvent::Data(format!("Launching with arguments {}", arguments_logs)));

        log::info!("Launching Minecraft with args: {:?}", args);

        let mut command = AsyncCommand::new(java_path);
        command.args(&args);
        command.current_dir(&working_dir);
        
        if options.detached.unwrap_or(false) {
            command.stdout(Stdio::null());
            command.stderr(Stdio::null());
        }

        let mut child = command.spawn()?;
        
        if !options.detached.unwrap_or(false) {
            let status = child.wait().await?;
            if !status.success() {
                return Err(LateJavaCoreError::Minecraft("Minecraft process failed".to_string()));
            }
            self.emit(LaunchEvent::Close("Minecraft closed".to_string()));
        }

        Ok(())
    }

    /// Obtener progreso de descarga
    pub async fn get_download_progress(&self) -> crate::downloader::DownloadProgress {
        // En una implementación real, esto retornaría el progreso actual
        crate::downloader::DownloadProgress {
            downloaded: 0,
            total: 0,
            speed: 0.0,
            percentage: 0.0,
        }
    }
}