late-java-core 2.2.9

A Rust library for launching Minecraft Java Edition
use crate::error::{Result, LateJavaCoreError};
use serde::{Deserialize, Serialize};
use std::path::Path;

/// Opciones para MinecraftAssets
#[derive(Debug, Clone)]
pub struct MinecraftAssetsOptions {
    pub path: String,
    pub instance: Option<String>,
}

/// JSON de versión de Minecraft
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionJson {
    pub asset_index: Option<AssetIndex>,
    pub assets: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetIndex {
    pub id: String,
    pub url: String,
}

/// Item de asset
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetItem {
    pub r#type: AssetType,
    pub path: String,
    pub content: Option<String>,
    pub sha1: Option<String>,
    pub size: Option<u64>,
    pub url: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AssetType {
    #[serde(rename = "CFILE")]
    CFile,
    #[serde(rename = "Assets")]
    Assets,
}

/// Clase para manejar assets de Minecraft
pub struct MinecraftAssets {
    asset_index: Option<AssetIndex>,
    options: MinecraftAssetsOptions,
}

impl MinecraftAssets {
    pub fn new(options: MinecraftAssetsOptions) -> Self {
        Self {
            asset_index: None,
            options,
        }
    }

    /// Obtener assets desde el JSON de versión
    pub async fn get_assets(&mut self, version_json: &VersionJson) -> Result<Vec<AssetItem>> {
        self.asset_index = version_json.asset_index.clone();
        
        if self.asset_index.is_none() {
            return Ok(Vec::new());
        }

        let asset_index = self.asset_index.as_ref().unwrap();

        // Obtener el JSON del índice de assets desde la URL remota
        let response = reqwest::get(&asset_index.url).await?;
        let data: serde_json::Value = response.json().await?;

        let mut assets_array = Vec::new();

        // Primer item es el archivo de índice en sí
        assets_array.push(AssetItem {
            r#type: AssetType::CFile,
            path: format!("assets/indexes/{}.json", asset_index.id),
            content: Some(serde_json::to_string(&data)?),
            sha1: None,
            size: None,
            url: None,
        });

        // Convertir la propiedad "objects" en una lista de assets individuales
        if let Some(objects) = data.get("objects").and_then(|o| o.as_object()) {
            for (_, obj) in objects {
                if let Some(obj_map) = obj.as_object() {
                    if let (Some(hash), Some(size)) = (
                        obj_map.get("hash").and_then(|h| h.as_str()),
                        obj_map.get("size").and_then(|s| s.as_u64())
                    ) {
                        assets_array.push(AssetItem {
                            r#type: AssetType::Assets,
                            sha1: Some(hash.to_string()),
                            size: Some(size),
                            path: format!("assets/objects/{}/{}", &hash[0..2], hash),
                            url: Some(format!("https://resources.download.minecraft.net/{}/{}", &hash[0..2], hash)),
                        });
                    }
                }
            }
        }

        Ok(assets_array)
    }

    /// Copiar assets legacy (para versiones más antiguas de Minecraft)
    pub fn copy_assets(&self, version_json: &VersionJson) -> Result<()> {
        let assets = version_json.assets.as_ref()
            .ok_or_else(|| LateJavaCoreError::Minecraft("No assets field in version JSON".to_string()))?;

        // Determinar el directorio legacy donde deben ir los recursos
        let legacy_directory = if let Some(instance) = &self.options.instance {
            format!("{}/instances/{}/resources", self.options.path, instance)
        } else {
            format!("{}/resources", self.options.path)
        };

        // La ruta al JSON del índice de assets local
        let path_assets = format!("{}/assets/indexes/{}.json", self.options.path, assets);
        if !Path::new(&path_assets).exists() {
            return Ok(()); // Nada que copiar si el archivo no existe
        }

        // Parsear el JSON del índice de assets
        let assets_content = std::fs::read_to_string(&path_assets)?;
        let assets_data: serde_json::Value = serde_json::from_str(&assets_content)?;

        // Cada entrada es [filePath, { hash, size }]
        if let Some(objects) = assets_data.get("objects").and_then(|o| o.as_object()) {
            for (file_path, hash_data) in objects {
                if let Some(hash_obj) = hash_data.as_object() {
                    if let Some(full_hash) = hash_obj.get("hash").and_then(|h| h.as_str()) {
                        let sub_hash = &full_hash[0..2];

                        // Directorio donde se almacena el archivo hasheado
                        let sub_asset_dir = format!("{}/assets/objects/{}", self.options.path, sub_hash);

                        // Si es necesario, crear los directorios correspondientes en la carpeta legacy
                        let path_segments: Vec<&str> = file_path.split('/').collect();
                        if path_segments.len() > 1 {
                            let dir_path = path_segments[0..path_segments.len()-1].join("/");
                            let full_dir_path = format!("{}/{}", legacy_directory, dir_path);
                            std::fs::create_dir_all(&full_dir_path)?;
                        }

                        // Copiar el archivo si no existe ya en la ubicación legacy
                        let source_file = format!("{}/{}", sub_asset_dir, full_hash);
                        let target_file = format!("{}/{}", legacy_directory, file_path);
                        
                        if !Path::new(&target_file).exists() {
                            std::fs::copy(&source_file, &target_file)?;
                        }
                    }
                }
            }
        }

        Ok(())
    }
}