/src/record.rs
use std::{cell::RefCell, fmt::Display, rc::Rc};

use num_derive::FromPrimitive;
use pretty_hex::{HexConfig, PrettyHex};

use crate::{error::OmfError, GroupInfo, OmfInfo, SegmentInfo};

#[derive(Debug)]
pub struct CommentType {
    pub no_purge: bool,
    pub no_list: bool,
}

#[derive(Debug)]
pub enum MAttrStart {
    NoStart,
    Start {
        end_data: u8,
        frame_datum: u8,
        target_datum: u8,
        target_displacement: u16,
    },
}

#[derive(Debug)]
pub struct PubName {
    pub name: String,
    pub public_offset: u16,
    pub type_index: u8,
}

#[derive(Debug)]
pub struct ExtName {
    pub name: String,
    pub type_index: u8,
}

#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive)]
pub enum SegmentAlignment {
    AbsoluteSegment = 0,
    RelocatableByteAligned = 1,
    RelocatableWordAligned = 2,
    RelocatableParagraphAligned = 3,
    RelocatablePageAligned = 4,
    RelocatableDWordAligned = 5,
}

impl Display for SegmentAlignment {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SegmentAlignment::AbsoluteSegment => write!(f, "absolute segment"),
            SegmentAlignment::RelocatableByteAligned => write!(f, "relocatable, byte aligned"),
            SegmentAlignment::RelocatableWordAligned => write!(f, "relocatable, word aligned"),
            SegmentAlignment::RelocatableParagraphAligned => {
                write!(f, "relocatable, paragraph aligned")
            }
            SegmentAlignment::RelocatablePageAligned => write!(f, "relocatable, page aligned"),
            SegmentAlignment::RelocatableDWordAligned => {
                write!(f, "relocatable, double word aligned")
            }
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, FromPrimitive)]
pub enum SegmentCombination {
    Private = 0,
    Public = 2,
    Public2 = 4,
    Stack = 5,
    Common = 6,
    Public3 = 7,
}

impl Display for SegmentCombination {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SegmentCombination::Private => write!(f, "private"),
            SegmentCombination::Public => write!(f, "public"),
            SegmentCombination::Public2 => write!(f, "public"),
            SegmentCombination::Stack => write!(f, "stack"),
            SegmentCombination::Common => write!(f, "common"),
            SegmentCombination::Public3 => write!(f, "public"),
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub struct AbsoluteSegmentAddress {
    pub frame_number: u16,
    pub offset: u8,
}

#[derive(Debug, Clone, Copy)]
pub struct SegmentAttributes {
    pub alignment: SegmentAlignment,
    pub combination: SegmentCombination,
    pub big: bool,
    pub bd32bit: bool,
    pub absolute_segment_address: Option<AbsoluteSegmentAddress>,
}

#[derive(Debug, Clone, Copy)]
pub struct GroupComponent {
    pub index: u8,
    pub segment_definition: u8,
}

#[derive(Debug)]
pub struct OmfRecord {
    pub record_type: u8,
    pub record_length: usize,
    pub data: OmfRecordData,
    pub checksum: u8,
    info: Rc<RefCell<OmfInfo>>,
}

impl OmfRecord {
    pub fn new(
        record_type: u8,
        record_length: usize,
        data: OmfRecordData,
        checksum: u8,
        info: Rc<RefCell<OmfInfo>>,
    ) -> OmfRecord {
        OmfRecord {
            record_type,
            record_length,
            data,
            checksum,
            info,
        }
    }

    pub fn name_from_index(&self, index: u8) -> Result<String, OmfError> {
        let i = (index as usize) - 1;
        let info = self.info.borrow();
        let s = info
            .names
            .get(i)
            .ok_or_else(|| OmfError::Value("name index not found"))?;
        Ok(s.clone())
    }

    pub fn get_segment(&self, index: u8) -> Result<SegmentInfo, OmfError> {
        let i = (index as usize) - 1;
        let info = self.info.borrow();
        let s = info
            .segments
            .get(i)
            .ok_or_else(|| OmfError::Value("segment index not found"))?;
        Ok(s.clone())
    }

    pub fn get_group(&self, index: u8) -> Result<GroupInfo, OmfError> {
        let i = (index as usize) - 1;
        let info = self.info.borrow();
        let s = info
            .groups
            .get(i)
            .ok_or_else(|| OmfError::Value("group index not found"))?;
        Ok(s.clone())
    }
}

impl Display for OmfRecord {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let cfg = HexConfig {
            group: 8,
            ..HexConfig::default()
        };

        writeln!(
            f,
            "Record type {:02X}h length {}",
            self.record_type, self.record_length
        )?;

        match &self.data {
            OmfRecordData::THeadr { name } => {
                writeln!(f, "Translator Header:")?;
                writeln!(f, "    Name: {name}")
            }
            OmfRecordData::Coment {
                comment_type,
                comment_class,
                comment_bytes,
            } => {
                writeln!(
                    f,
                    "Comment - {}{}class {:02X}",
                    if comment_type.no_purge {
                        "no purge "
                    } else {
                        ""
                    },
                    if comment_type.no_list { "no list " } else { "" },
                    comment_class
                )?;
                writeln!(f, "{:?}", comment_bytes.hex_conf(cfg))
            }
            OmfRecordData::ModEnd { main, start } => {
                writeln!(f, "Module End{}", if *main { " (MAIN)" } else { "" })?;
                match start {
                    MAttrStart::NoStart => (),
                    MAttrStart::Start {
                        end_data,
                        frame_datum,
                        target_datum,
                        target_displacement,
                    } => {
                        writeln!(f, "    End data: {end_data:02X}, frame: {frame_datum:02X}, target: {target_datum:02X}, displacement: {target_displacement:04X}")?;
                    }
                }
                Ok(())
            }
            OmfRecordData::ExtDef { names } => {
                writeln!(f, "External Names Definition")?;
                for (i, n) in names.iter().enumerate() {
                    writeln!(f, "    {i:<4} {} type {}", n.name, n.type_index)?;
                }
                Ok(())
            }
            OmfRecordData::PubDef {
                base_group_index,
                base_segment_index,
                base_frame,
                names,
            } => {
                writeln!(f, "Public Names Definition")?;
                if *base_group_index == 0 && *base_segment_index == 0 {
                    writeln!(f, "    Base Frame: {base_frame:04X}")?;
                } else {
                    if *base_group_index == 0 {
                        writeln!(f, "    Base Group: None")?;
                    } else {
                        let base_group = self.get_group(*base_group_index).expect("group index");
                        writeln!(
                            f,
                            "    Base Group: {} ({})",
                            self.name_from_index(base_group.group_name_index)
                                .expect("name index"),
                            base_group_index
                        )?;
                    }
                    let base_segment = self
                        .get_segment(*base_segment_index)
                        .expect("segment index");
                    writeln!(
                        f,
                        "    Base Segment: {} ({})",
                        self.name_from_index(base_segment.segment_name_index)
                            .expect("name lookup"),
                        base_segment_index
                    )?;
                }
                writeln!(f, "    Names:")?;
                for n in names {
                    writeln!(
                        f,
                        "        {} offset {:04X} type {}",
                        n.name, n.public_offset, n.type_index
                    )?;
                }
                Ok(())
            }
            OmfRecordData::LNames { names } => {
                writeln!(f, "List of Names")?;
                for (i, n) in names.iter().enumerate() {
                    writeln!(f, "    {:<4} {}", i + 1, n)?;
                }
                Ok(())
            }
            OmfRecordData::SegDef {
                segment_attributes,
                segment_length,
                segment_name_index,
                class_name_index,
                overlay_name_index,
            } => {
                writeln!(
                    f,
                    "Segment Definition - {} ({})",
                    self.name_from_index(*segment_name_index)
                        .expect("name lookup"),
                    *segment_name_index
                )?;
                writeln!(
                    f,
                    "    Attributes: {}; {} combination{}{}",
                    segment_attributes.alignment,
                    segment_attributes.combination,
                    if segment_attributes.big { "; BIG" } else { "" },
                    if segment_attributes.bd32bit {
                        "; USE32"
                    } else {
                        ""
                    }
                )?;
                writeln!(f, "    Segment length: {segment_length:04X}")?;
                writeln!(
                    f,
                    "    Class name: {} ({})",
                    self.name_from_index(*class_name_index)
                        .expect("name lookup"),
                    class_name_index
                )?;
                writeln!(
                    f,
                    "    Overlay name: {} ({})",
                    self.name_from_index(*overlay_name_index)
                        .expect("name lookup"),
                    overlay_name_index
                )?;
                Ok(())
            }
            OmfRecordData::GrpDef {
                group_name_index,
                segment_definitions,
            } => {
                writeln!(
                    f,
                    "Group Definition - {} ({})",
                    self.name_from_index(*group_name_index)
                        .expect("name lookup"),
                    group_name_index
                )?;
                writeln!(f, "    Segments:")?;
                for (i, s) in segment_definitions.iter().enumerate() {
                    let segment = self
                        .get_segment(s.segment_definition)
                        .expect("segment index");
                    writeln!(
                        f,
                        "        {:<4} {} ({})",
                        i,
                        self.name_from_index(segment.segment_name_index)
                            .expect("name lookup"),
                        s.segment_definition
                    )?;
                }
                Ok(())
            }
            OmfRecordData::LEData {
                segment_index,
                enumerated_data_offset,
                data,
            } => {
                let segment = self.get_segment(*segment_index).expect("segment index");
                writeln!(
                    f,
                    "Logical Enumerated Data - {} ({}) offset {:04X}h",
                    self.name_from_index(segment.segment_name_index)
                        .expect("name lookup"),
                    segment_index,
                    enumerated_data_offset
                )?;
                writeln!(f, "{:?}", data.hex_conf(cfg))
            }
            OmfRecordData::Unknown { data } => {
                writeln!(f, "Unknown Data")?;
                writeln!(f, "{:?}", data.hex_conf(cfg))
            }
        }
    }
}

#[derive(Debug)]
pub enum OmfRecordData {
    THeadr {
        // 80
        name: String,
    },
    Coment {
        // 88
        comment_type: CommentType,
        comment_class: u8,
        comment_bytes: Vec<u8>,
    },
    ModEnd {
        // 8A
        main: bool,
        start: MAttrStart,
    },
    ExtDef {
        // 8C
        names: Vec<ExtName>,
    },
    PubDef {
        // 90
        base_group_index: u8,
        base_segment_index: u8,
        base_frame: u16,
        names: Vec<PubName>,
    },
    LNames {
        // 96
        names: Vec<String>,
    },
    SegDef {
        // 98
        segment_attributes: SegmentAttributes,
        segment_length: u16,
        segment_name_index: u8,
        class_name_index: u8,
        overlay_name_index: u8,
    },
    GrpDef {
        // 9A
        group_name_index: u8,
        segment_definitions: Vec<GroupComponent>,
    },
    LEData {
        // A0
        segment_index: u8,
        enumerated_data_offset: u16,
        data: Vec<u8>,
    },
    Unknown {
        data: Vec<u8>,
    },
}