use alloc::vec::Vec;

use crate::common::Encoding;
use crate::write::{
    AbbreviationTable, LineProgram, LineString, LineStringTable, Result, Sections, StringTable,
    Unit, UnitTable, Writer,
};

/// Writable DWARF information for more than one unit.
#[derive(Debug, Default)]
pub struct Dwarf {
    /// A table of units. These are primarily stored in the `.debug_info` section,
    /// but they also contain information that is stored in other sections.
    pub units: UnitTable,

    /// Extra line number programs that are not associated with a unit.
    ///
    /// These should only be used when generating DWARF5 line-only debug
    /// information.
    pub line_programs: Vec<LineProgram>,

    /// A table of strings that will be stored in the `.debug_line_str` section.
    pub line_strings: LineStringTable,

    /// A table of strings that will be stored in the `.debug_str` section.
    pub strings: StringTable,
}

impl Dwarf {
    /// Create a new `Dwarf` instance.
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }

    /// Write the DWARF information to the given sections.
    pub fn write<W: Writer>(&mut self, sections: &mut Sections<W>) -> Result<()> {
        let line_strings = self.line_strings.write(&mut sections.debug_line_str)?;
        let strings = self.strings.write(&mut sections.debug_str)?;
        self.units.write(sections, &line_strings, &strings)?;
        for line_program in &self.line_programs {
            line_program.write(
                &mut sections.debug_line,
                line_program.encoding(),
                &line_strings,
                &strings,
            )?;
        }
        Ok(())
    }

    /// Get a reference to the data for a line string.
    pub fn get_line_string<'a>(&'a self, string: &'a LineString) -> &'a [u8] {
        string.get(&self.strings, &self.line_strings)
    }
}

/// Writable DWARF information for a single unit.
#[derive(Debug)]
pub struct DwarfUnit {
    /// A unit. This is primarily stored in the `.debug_info` section,
    /// but also contains information that is stored in other sections.
    pub unit: Unit,

    /// A table of strings that will be stored in the `.debug_line_str` section.
    pub line_strings: LineStringTable,

    /// A table of strings that will be stored in the `.debug_str` section.
    pub strings: StringTable,
}

impl DwarfUnit {
    /// Create a new `DwarfUnit`.
    ///
    /// Note: you should set `self.unit.line_program` after creation.
    /// This cannot be done earlier because it may need to reference
    /// `self.line_strings`.
    pub fn new(encoding: Encoding) -> Self {
        let unit = Unit::new(encoding, LineProgram::none());
        DwarfUnit {
            unit,
            line_strings: LineStringTable::default(),
            strings: StringTable::default(),
        }
    }

    /// Write the DWARf information to the given sections.
    pub fn write<W: Writer>(&mut self, sections: &mut Sections<W>) -> Result<()> {
        let line_strings = self.line_strings.write(&mut sections.debug_line_str)?;
        let strings = self.strings.write(&mut sections.debug_str)?;

        let abbrev_offset = sections.debug_abbrev.offset();
        let mut abbrevs = AbbreviationTable::default();

        self.unit.write(
            sections,
            abbrev_offset,
            &mut abbrevs,
            &line_strings,
            &strings,
        )?;
        // None should exist because we didn't give out any UnitId.
        assert!(sections.debug_info_refs.is_empty());
        assert!(sections.debug_loc_refs.is_empty());
        assert!(sections.debug_loclists_refs.is_empty());

        abbrevs.write(&mut sections.debug_abbrev)?;
        Ok(())
    }

    /// Get a reference to the data for a line string.
    pub fn get_line_string<'a>(&'a self, string: &'a LineString) -> &'a [u8] {
        string.get(&self.strings, &self.line_strings)
    }
}

#[cfg(feature = "read")]
pub(crate) mod convert {
    use super::*;
    use crate::read::{self, Reader};
    use crate::write::{Address, ConvertResult};

    impl Dwarf {
        /// Create a `write::Dwarf` by converting a `read::Dwarf`.
        ///
        /// `convert_address` is a function to convert read addresses into the `Address`
        /// type. For non-relocatable addresses, this function may simply return
        /// `Address::Constant(address)`. For relocatable addresses, it is the caller's
        /// responsibility to determine the symbol and addend corresponding to the address
        /// and return `Address::Symbol { symbol, addend }`.
        pub fn from<R: Reader<Offset = usize>>(
            dwarf: &read::Dwarf<R>,
            convert_address: &dyn Fn(u64) -> Option<Address>,
        ) -> ConvertResult<Dwarf> {
            let mut line_strings = LineStringTable::default();
            let mut strings = StringTable::default();
            let units = UnitTable::from(dwarf, &mut line_strings, &mut strings, convert_address)?;
            // TODO: convert the line programs that were not referenced by a unit.
            let line_programs = Vec::new();
            Ok(Dwarf {
                units,
                line_programs,
                line_strings,
                strings,
            })
        }
    }
}
