//! Debug structs

use crate as androscalpel_serializer;
use crate::{ReadSeek, Result, Serializable, SerializableUntil, Sleb128, Uleb128, Uleb128p1};
use std::io::Write;

/// <https://source.android.com/docs/core/runtime/dex-format#debug-info-item>
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DebugInfoItem {
    pub line_start: Uleb128,
    //pub parameters_size: Uleb128,
    pub parameter_names: Vec<Uleb128p1>,
    /// List of opcode. Notice that the trailling [`DbgBytecode::EndSequence`]
    /// is not stored in this vec.
    pub bytecode: Vec<DbgBytecode>,
}

#[derive(Serializable, Debug, PartialEq, Eq, Copy, Clone)]
#[prefix_type(u8)]
pub enum DbgBytecode {
    #[prefix(0x00)]
    EndSequence,
    #[prefix(0x01)]
    AdvancePC { addr_diff: Uleb128 },
    #[prefix(0x02)]
    AdvanceLine { line_diff: Sleb128 },
    #[prefix(0x03)]
    StartLocal {
        register_num: Uleb128,
        name_idx: Uleb128p1,
        type_idx: Uleb128p1,
    },
    #[prefix(0x04)]
    StartLocalExtended {
        register_num: Uleb128,
        name_idx: Uleb128p1,
        type_idx: Uleb128p1,
        sig_idx: Uleb128p1,
    },
    #[prefix(0x05)]
    EndLocal { register_num: Uleb128 },
    #[prefix(0x06)]
    RestartLocal { register_num: Uleb128 },
    #[prefix(0x07)]
    SetPrologueEnd,
    #[prefix(0x08)]
    SetEpilogueBegin,
    #[prefix(0x09)]
    SetFile { name_idx: Uleb128p1 },
    #[default_variant]
    SpecialOpcode(u8),
}

impl DebugInfoItem {
    pub fn parameters_size_field(&self) -> Uleb128 {
        Uleb128(self.parameter_names.len() as u32)
    }
}

impl Serializable for DebugInfoItem {
    fn serialize(&self, output: &mut dyn Write) -> Result<()> {
        self.line_start.serialize(output)?;
        self.parameters_size_field().serialize(output)?;
        for item in &self.parameter_names {
            item.serialize(output)?;
        }
        self.bytecode.serialize(output, DbgBytecode::EndSequence)?;
        Ok(())
    }
    fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
        let line_start = Uleb128::deserialize(input)?;
        let Uleb128(parameters_size) = Uleb128::deserialize(input)?;
        let mut parameter_names = vec![];
        for _ in 0..parameters_size {
            parameter_names.push(Uleb128p1::deserialize(input)?);
        }
        let bytecode = Vec::<DbgBytecode>::deserialize(input, DbgBytecode::EndSequence)?;
        Ok(Self {
            line_start,
            parameter_names,
            bytecode,
        })
    }
    fn size(&self) -> usize {
        self.line_start.size()
            + self.parameters_size_field().size()
            + self
                .parameter_names
                .iter()
                .map(|param| param.size())
                .sum::<usize>()
            + self.bytecode.size(DbgBytecode::EndSequence)
    }
}

#[cfg(test)]
mod test {
    use super::DbgBytecode::*;
    use super::*;

    #[test]
    fn test_debug_reserialize() {
        let debug = DebugInfoItem {
            line_start: Uleb128(2902),
            parameter_names: vec![],
            bytecode: vec![
                SpecialOpcode(14),
                AdvanceLine {
                    line_diff: Sleb128(-1551),
                },
                AdvancePC {
                    addr_diff: Uleb128(51),
                },
                SpecialOpcode(14),
            ],
        };
        assert_eq!(
            debug,
            DebugInfoItem::deserialize_from_slice(&debug.serialize_to_vec().unwrap()).unwrap()
        );
    }

    #[test]
    fn test_advance_line_reserialize() {
        let advance_line = AdvanceLine {
            line_diff: Sleb128(-1551),
        };
        assert_eq!(
            advance_line,
            DbgBytecode::deserialize_from_slice(&advance_line.serialize_to_vec().unwrap()).unwrap()
        );
    }
}
