mod cmd;
mod inhx;
mod inhx32;
mod raw;
mod debug;

pub use cmd::*;
pub use inhx::*;
pub use inhx32::*;
pub use raw::*;
pub use debug::*;

use crate::*;

use log::*;


#[derive(Clone, Copy, PartialEq)]
pub enum Format {
    Cmd,
    Debug,
    Inhx,
    Inhx32,
    Raw,
    Source,
}

impl Format {
    pub fn from_str(string: &str) -> Self {
        match string {
            "cmd" => Self::Cmd,
            "debug" => Self::Debug,
            "inhx" => Self::Inhx,
            "inhx32" => Self::Inhx32,
            "raw" => Self::Raw,
            "source" => Self::Source,
            _ => fatal!("Unknown format '{string}', expected 'debug', 'inhx', 'inhx32', 'raw', or 'source'"),
        }
    }
}

impl std::fmt::Display for Format {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        let string = match self {
            Self::Cmd => "cmd",
            Self::Debug => "debug",
            Self::Inhx => "inhx",
            Self::Inhx32 => "inhx32",
            Self::Raw => "raw",
            Self::Source => "source",
        };
        write!(f, "{string}")
    }
}


pub enum FormatError {
    /// (expected, received)
    AddressTooLarge(usize, usize),
    /// (expected, received)
    WordTooWide(u32, u32, SourceSpan),
    ///
    ExpectedFixedWidth,
}

pub fn report_format_error(error: &FormatError, format: Format, source_code: &str) {
    match error {
        FormatError::AddressTooLarge(expected, received) =>
            error!("The {format} format requires that addresses do not exceed {expected}, but the address {received} was reached"),
        FormatError::WordTooWide(expected, received, source) => {
            let message = format!("The {format} format requires that words are no wider than {expected} bits, but a {received} bit word was found");
            let context = Context { source_code, source };
            report_source_issue(LogLevel::Error, &context, &message);
        }
        FormatError::ExpectedFixedWidth =>
            error!("The {format} format requires all words to be the same width"),
    }
    std::process::exit(1);
}



pub struct InhxRecord {
    bytes: Vec<u8>,
}

impl InhxRecord {
    pub fn new() -> Self {
        Self { bytes: Vec::new() }
    }

    pub fn byte(&mut self, byte: u8) {
        self.bytes.push(byte);
    }

    pub fn be_double(&mut self, double: u16) {
        let [high, low] = double.to_be_bytes();
        self.byte(high);
        self.byte(low);
    }

    pub fn le_double(&mut self, double: u16) {
        let [high, low] = double.to_be_bytes();
        self.byte(low);
        self.byte(high);
    }

    pub fn to_string(self) -> String {
        let mut sum: u8 = 0;
        for byte in &self.bytes {
            sum = sum.wrapping_add(*byte);
        }
        let checksum = sum.wrapping_neg();
        let mut output = String::new();
        for byte in &self.bytes {
            output.push_str(&format!("{byte:0>2X}"));
        }
        format!(":{output}{checksum:0>2X}\n")
    }

    pub fn to_string_plain(self) -> String {
        let mut output = String::new();
        for byte in &self.bytes {
            output.push_str(&format!("{byte:0>2X}"));
        }
        format!("{output}\n")
    }
}


pub fn calculate_fixed_width(segments: &[Segment]) -> Option<u32> {
    let mut width = None;
    for segment in segments {
        for word in &segment.words {
            let word_width = word.value.width;
            match width {
                Some(width) => if word_width != width {
                    return None;
                }
                None => width = Some(word_width),
            }
        }
    }
    return width.or(Some(0));
}