//! Escu:de List File (.bin)
use crate::ext::io::*;
use crate::scripts::base::*;
use crate::types::*;
use crate::utils::encoding::{decode_to_string, encode_string};
use crate::utils::struct_pack::*;
use anyhow::Result;
use msg_tool_macro::*;
use serde::{Deserialize, Serialize};
use std::io::{Read, Seek, Write};

#[derive(Debug)]
/// Escu:de List Builder
pub struct EscudeBinListBuilder {}

impl EscudeBinListBuilder {
    /// Creates a new instance of `EscudeBinListBuilder`
    pub const fn new() -> Self {
        EscudeBinListBuilder {}
    }
}

impl ScriptBuilder for EscudeBinListBuilder {
    fn default_encoding(&self) -> Encoding {
        Encoding::Cp932
    }

    fn build_script(
        &self,
        data: Vec<u8>,
        filename: &str,
        encoding: Encoding,
        _archive_encoding: Encoding,
        config: &ExtraConfig,
        _archive: Option<&Box<dyn Script>>,
    ) -> Result<Box<dyn Script + Send + Sync>> {
        Ok(Box::new(EscudeBinList::new(
            data, filename, encoding, config,
        )?))
    }

    fn extensions(&self) -> &'static [&'static str] {
        &["bin"]
    }

    fn script_type(&self) -> &'static ScriptType {
        &ScriptType::EscudeList
    }

    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
        if buf_len > 4 && buf.starts_with(b"LIST") {
            return Some(255);
        }
        None
    }

    fn can_create_file(&self) -> bool {
        true
    }

    fn create_file<'a>(
        &'a self,
        filename: &'a str,
        writer: Box<dyn WriteSeek + 'a>,
        encoding: Encoding,
        file_encoding: Encoding,
        config: &ExtraConfig,
    ) -> Result<()> {
        create_file(
            filename,
            writer,
            encoding,
            file_encoding,
            config.custom_yaml,
        )
    }
}

#[derive(Debug)]
/// Escu:de Binary List
pub struct EscudeBinList {
    /// List of entries in the Escu:de list
    pub entries: Vec<ListEntry>,
    custom_yaml: bool,
}

impl EscudeBinList {
    /// Creates a new `EscudeBinList` from the given data
    ///
    /// * `data` - The reader to read the data from
    /// * `filename` - The name of the file
    /// * `encoding` - The encoding
    /// * `config` - Extra configuration options
    pub fn new(
        data: Vec<u8>,
        filename: &str,
        encoding: Encoding,
        config: &ExtraConfig,
    ) -> Result<Self> {
        let mut reader = MemReader::new(data);
        let mut magic = [0; 4];
        reader.read_exact(&mut magic)?;
        if &magic != b"LIST" {
            return Err(anyhow::anyhow!("Invalid Escude list file format"));
        }
        let wsize = reader.read_u32()?;
        let mut entries = Vec::new();
        loop {
            let current = reader.stream_position()?;
            if current as usize >= wsize as usize + 8 {
                break;
            }
            let id = reader.read_u32()?;
            let size = reader.read_u32()?;
            let data = reader.read_exact_vec(size as usize)?;
            entries.push(ListEntry {
                id: id,
                data: ListData::Unknown(data),
            });
        }
        let mut s = EscudeBinList {
            entries,
            custom_yaml: config.custom_yaml,
        };
        match s.try_decode(filename, encoding) {
            Ok(_) => {}
            Err(e) => {
                eprintln!("WARN: Failed to decode Escude list: {}", e);
                crate::COUNTER.inc_warning();
            }
        }
        Ok(s)
    }

    /// Attempts to decode the entries in the list based on the filename and encoding
    ///
    /// * `filename` - The name of the file
    /// * `encoding` - The encoding to use for decoding
    pub fn try_decode(&mut self, filename: &str, encoding: Encoding) -> Result<()> {
        let filename = std::path::Path::new(filename);
        if let Some(filename) = filename.file_name() {
            let filename = filename.to_ascii_lowercase();
            if filename == "enum_scr.bin" {
                for ent in self.entries.iter_mut() {
                    let id = ent.id;
                    if let ListData::Unknown(unk) = &ent.data {
                        let mut reader = MemReader::new(unk.clone());
                        let mut element_size = if id == 0 {
                            132
                        } else if id == 1 {
                            100
                        } else if id == 2 {
                            36
                        } else if id == 3 {
                            104
                        } else if id == 9999 {
                            1
                        } else {
                            return Err(anyhow::anyhow!("Unknown enum source ID: {}", id));
                        };
                        let len = unk.len();
                        let mut script_old = false;
                        if id == 0 && len % element_size != 0 {
                            element_size = 128;
                            script_old = true;
                        }
                        if len % element_size != 0 {
                            return Err(anyhow::anyhow!(
                                "Invalid enum source length: {} for ID: {}",
                                len,
                                id
                            ));
                        }
                        let count = len / element_size;
                        let data_entry = match id {
                            0 => {
                                if script_old {
                                    ListData::Scr(EnumScr::Scripts2(
                                        reader.read_struct_vec::<ScriptT2>(
                                            count, false, encoding, &None,
                                        )?,
                                    ))
                                } else {
                                    ListData::Scr(EnumScr::Scripts(
                                        reader.read_struct_vec::<ScriptT>(
                                            count, false, encoding, &None,
                                        )?,
                                    ))
                                }
                            }
                            1 => ListData::Scr(EnumScr::Names(
                                reader.read_struct_vec::<NameT>(count, false, encoding, &None)?,
                            )),
                            2 => ListData::Scr(EnumScr::Vars(
                                reader.read_struct_vec::<VarT>(count, false, encoding, &None)?,
                            )),
                            3 => ListData::Scr(EnumScr::Scenes(
                                reader.read_struct_vec::<SceneT>(count, false, encoding, &None)?,
                            )),
                            9999 => {
                                // Special case for unknown enum source ID
                                ListData::Unknown(unk.clone())
                            }
                            _ => return Err(anyhow::anyhow!("Unknown enum source ID: {}", id)),
                        };
                        ent.data = data_entry;
                    }
                }
            } else if filename == "enum_gfx.bin" {
                for ent in self.entries.iter_mut() {
                    let id = ent.id;
                    if let ListData::Unknown(unk) = &ent.data {
                        let mut reader = MemReader::new(unk.clone());
                        let element_size = if id == 0 {
                            248
                        } else if id == 1 {
                            248
                        } else if id == 2 {
                            248
                        } else if id == 3 {
                            112
                        } else if id == 4 {
                            32
                        } else if id == 9999 {
                            1
                        } else {
                            return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id));
                        };
                        let len = unk.len();
                        if len % element_size != 0 {
                            return Err(anyhow::anyhow!(
                                "Invalid enum gfx length: {} for ID: {}",
                                len,
                                id
                            ));
                        }
                        let count = len / element_size;
                        let data_entry = match id {
                            0 => ListData::Gfx(EnumGfx::Bgs(
                                reader.read_struct_vec::<BgT>(count, false, encoding, &None)?,
                            )),
                            1 => ListData::Gfx(EnumGfx::Evs(
                                reader.read_struct_vec::<EvT>(count, false, encoding, &None)?,
                            )),
                            2 => ListData::Gfx(EnumGfx::Sts(
                                reader.read_struct_vec::<StT>(count, false, encoding, &None)?,
                            )),
                            3 => ListData::Gfx(EnumGfx::Efxs(
                                reader.read_struct_vec::<EfxT>(count, false, encoding, &None)?,
                            )),
                            4 => ListData::Gfx(EnumGfx::Locs(
                                reader.read_struct_vec::<LocT>(count, false, encoding, &None)?,
                            )),
                            9999 => {
                                // Special case for unknown enum gfx ID
                                ListData::Unknown(unk.clone())
                            }
                            _ => return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id)),
                        };
                        ent.data = data_entry;
                    }
                }
            } else if filename == "enum_snd.bin" {
                for ent in self.entries.iter_mut() {
                    let id = ent.id;
                    if let ListData::Unknown(unk) = &ent.data {
                        let mut reader = MemReader::new(unk.clone());
                        let element_size = if id == 0 {
                            196
                        } else if id == 1 {
                            128
                        } else if id == 2 {
                            128
                        } else if id == 3 {
                            128
                        } else if id == 9999 {
                            1
                        } else {
                            return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id));
                        };
                        let len = unk.len();
                        if len % element_size != 0 {
                            return Err(anyhow::anyhow!(
                                "Invalid enum sound length: {} for ID: {}",
                                len,
                                id
                            ));
                        }
                        let count = len / element_size;
                        let data_entry = match id {
                            0 => ListData::Snd(EnumSnd::Bgm(
                                reader.read_struct_vec::<BgmT>(count, false, encoding, &None)?,
                            )),
                            1 => ListData::Snd(EnumSnd::Amb(
                                reader.read_struct_vec::<AmbT>(count, false, encoding, &None)?,
                            )),
                            2 => ListData::Snd(EnumSnd::Se(
                                reader.read_struct_vec::<SeT>(count, false, encoding, &None)?,
                            )),
                            3 => ListData::Snd(EnumSnd::Sfx(
                                reader.read_struct_vec::<SfxT>(count, false, encoding, &None)?,
                            )),
                            9999 => {
                                // Special case for unknown enum sound ID
                                ListData::Unknown(unk.clone())
                            }
                            _ => return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id)),
                        };
                        ent.data = data_entry;
                    }
                }
            }
        }
        Ok(())
    }
}

fn create_file<'a>(
    custom_filename: &'a str,
    mut writer: Box<dyn WriteSeek + 'a>,
    encoding: Encoding,
    output_encoding: Encoding,
    yaml: bool,
) -> Result<()> {
    let input = crate::utils::files::read_file(custom_filename)?;
    let s = decode_to_string(output_encoding, &input, true)?;
    let entries: Vec<ListEntry> = if yaml {
        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
    } else {
        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
    };
    writer.write_all(b"LIST")?;
    writer.write_u32(0)?; // Placeholder for size
    let mut total_size = 0;
    for entry in entries {
        let cur_pos = writer.stream_position()?;
        writer.write_u32(entry.id)?;
        writer.write_u32(0)?; // Placeholder for size
        entry.data.pack(&mut writer, false, encoding, &None)?;
        let end_pos = writer.stream_position()?;
        let size = (end_pos - cur_pos - 8) as u32; // 8 bytes for id and size
        writer.seek(std::io::SeekFrom::Start(cur_pos + 4))?; // Seek to size position
        writer.write_u32(size)?;
        writer.seek(std::io::SeekFrom::Start(end_pos))?; // Seek to end
        total_size += size + 8;
    }
    writer.seek(std::io::SeekFrom::Start(4))?; // Seek back to size position
    writer.write_u32(total_size)?;
    writer.flush()?;
    Ok(())
}

impl Script for EscudeBinList {
    fn default_output_script_type(&self) -> OutputScriptType {
        OutputScriptType::Custom
    }

    fn is_output_supported(&self, output: OutputScriptType) -> bool {
        matches!(output, OutputScriptType::Custom)
    }

    fn default_format_type(&self) -> FormatOptions {
        FormatOptions::None
    }

    fn custom_output_extension(&self) -> &'static str {
        if self.custom_yaml { "yaml" } else { "json" }
    }

    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
        let s = if self.custom_yaml {
            serde_yaml_ng::to_string(&self.entries)
                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
        } else {
            serde_json::to_string_pretty(&self.entries)
                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
        };
        let mut writer = crate::utils::files::write_file(filename)?;
        let s = encode_string(encoding, &s, false)?;
        writer.write_all(&s)?;
        writer.flush()?;
        Ok(())
    }

    fn custom_import<'a>(
        &'a self,
        custom_filename: &'a str,
        writer: Box<dyn WriteSeek + 'a>,
        encoding: Encoding,
        output_encoding: Encoding,
    ) -> Result<()> {
        create_file(
            custom_filename,
            writer,
            encoding,
            output_encoding,
            self.custom_yaml,
        )
    }
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Script entry in the Escu:de list
pub struct ScriptT {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// File name
    pub file: String,
    /// Script ID
    pub source: u32,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Script title
    pub title: String,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Script entry in the Escu:de list
pub struct ScriptT2 {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// File name
    pub file: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Script title
    pub title: String,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Name entry in the Escu:de list
pub struct NameT {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Name of the character
    pub text: String,
    /// Text color
    pub color: u32,
    #[fstring = 32]
    #[fstring_pad = 0x20]
    /// Face image file name
    pub face: String,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Variable entry in the Escu:de list
pub struct VarT {
    /// Variable name
    #[fstring = 32]
    #[fstring_pad = 0x20]
    pub name: String,
    /// Variable value
    pub value: u16,
    /// Variable flag
    pub flag: u16,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Scene entry in the Escu:de list
pub struct SceneT {
    /// The scene script ID
    pub script: u32,
    /// The scene name
    #[fstring = 64]
    #[fstring_pad = 0x20]
    pub name: String,
    /// The scene thumbail image file name
    #[fstring = 32]
    #[fstring_pad = 0x20]
    pub thumbnail: String,
    /// The scene order in the scene (Extra)
    pub order: i32,
}

#[derive(Debug, Serialize, Deserialize, StructPack)]
#[serde(tag = "type", content = "data")]
/// Enum for different types of script data in the Escu:de list (enum._scr.bin)
pub enum EnumScr {
    /// Scripts data
    Scripts(Vec<ScriptT>),
    /// Scripts data (old)
    Scripts2(Vec<ScriptT2>),
    /// Names data
    Names(Vec<NameT>),
    /// Variables data
    Vars(Vec<VarT>),
    /// Scenes data
    Scenes(Vec<SceneT>),
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Background entry in the Escu:de list
pub struct BgT {
    /// Background image name
    #[fstring = 32]
    #[fstring_pad = 0x20]
    name: String,
    /// Background image file name
    #[fstring = 64]
    #[fstring_pad = 0x20]
    file: String,
    #[fstring = 128]
    #[fstring_pad = 0x20]
    /// Background options
    option: String,
    /// Background covered flag
    coverd: u32,
    /// Background color
    color: u32,
    /// Background ID
    id: u32,
    /// Background location ID
    loc: u32,
    /// Background order in the scene
    order: i32,
    /// Background link ID
    link: u32,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Event image entry in the Escu:de list
pub struct EvT {
    /// Event image name
    #[fstring = 32]
    #[fstring_pad = 0x20]
    name: String,
    /// Event image file name
    #[fstring = 64]
    #[fstring_pad = 0x20]
    file: String,
    #[fstring = 128]
    #[fstring_pad = 0x20]
    /// Event image options
    option: String,
    /// Event image covered flag
    coverd: u32,
    /// Event image color
    color: u32,
    /// Event image ID
    id: u32,
    /// Event image location ID
    loc: u32,
    /// Event image order in the scene
    order: i32,
    /// Event image link ID
    link: u32,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Stage entry in the Escu:de list
pub struct StT {
    #[fstring = 32]
    #[fstring_pad = 0x20]
    /// Stage name
    name: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Stage file name
    file: String,
    #[fstring = 128]
    #[fstring_pad = 0x20]
    /// Stage options
    option: String,
    /// Stage covered flag
    coverd: u32,
    /// Stage color
    color: u32,
    /// Stage ID
    id: u32,
    /// Stage location ID
    loc: u32,
    /// Stage order in the scene
    order: i32,
    /// Stage link ID
    link: u32,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Effect image entry in the Escu:de list
pub struct EfxT {
    /// Effect image name
    #[fstring = 32]
    #[fstring_pad = 0x20]
    name: String,
    /// Effect image file name
    #[fstring = 64]
    #[fstring_pad = 0x20]
    file: String,
    /// Effect image options
    spot: i32,
    /// Effect image x position
    dx: i32,
    /// Effect image y position
    dy: i32,
    /// Effect image loop flag
    r#loop: bool,
    #[fvec = 3]
    #[serde(skip, default = "exft_padding")]
    /// padding for alignment
    padding: Vec<u8>,
}

fn exft_padding() -> Vec<u8> {
    vec![0; 3]
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Point
pub struct Point {
    /// X coordinate
    x: i16,
    /// Y coordinate
    y: i16,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Location entry in the Escu:de list
pub struct LocT {
    /// List of points
    #[fvec = 8]
    pt: Vec<Point>,
}

#[derive(Debug, Serialize, Deserialize, StructPack)]
#[serde(tag = "type", content = "data")]
/// Enum for different types of graphics data in the Escu:de list (enum_gfx.bin)
pub enum EnumGfx {
    /// Backgrounds data
    Bgs(Vec<BgT>),
    /// Event images data
    Evs(Vec<EvT>),
    /// Stages data
    Sts(Vec<StT>),
    /// Effects data
    Efxs(Vec<EfxT>),
    /// Locations data
    Locs(Vec<LocT>),
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Background music entry in the Escu:de list
pub struct BgmT {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Background music name
    pub name: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Background music file name
    pub file: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Background music title
    pub title: String,
    /// Background music order in the scene
    pub order: i32,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Ambient sound entry in the Escu:de list
pub struct AmbT {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Ambient sound name
    pub name: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Ambient sound file name
    pub file: String,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Sound effect entry in the Escu:de list
pub struct SeT {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Sound effect name
    pub name: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// Sound effect file name
    pub file: String,
}

#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
/// Sound effect (SFX) entry in the Escu:de list
pub struct SfxT {
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// SFX name
    pub name: String,
    #[fstring = 64]
    #[fstring_pad = 0x20]
    /// SFX file name
    pub file: String,
}

#[derive(Debug, Serialize, Deserialize, StructPack)]
#[serde(tag = "type", content = "data")]
/// Enum for different types of sound data in the Escu:de list (enum_snd.bin)
pub enum EnumSnd {
    /// Background music data
    Bgm(Vec<BgmT>),
    /// Ambient sound data
    Amb(Vec<AmbT>),
    /// Sound effect data
    Se(Vec<SeT>),
    /// Sound effect (SFX) data
    Sfx(Vec<SfxT>),
}

#[derive(Debug, Serialize, Deserialize, StructPack)]
#[serde(tag = "type", content = "data")]
/// Enum for different types of data in the Escu:de list
pub enum ListData {
    /// Script data
    Scr(EnumScr),
    /// Graphics data
    Gfx(EnumGfx),
    /// Sound data
    Snd(EnumSnd),
    /// Unknown data
    Unknown(Vec<u8>),
}

#[derive(Debug, Serialize, Deserialize)]
/// Entry in the Escu:de list
pub struct ListEntry {
    id: u32,
    /// The data associated with the entry
    pub data: ListData,
}
