use std::borrow::Borrow;

use bit_struct::u10;

use crate::Labels;
use crate::assembly_error::AssemblyError;
use crate::components::address::{AddrRepr, Address};
use crate::instruction::{BinInstr, Instruction};

/// Converts instructions to binary data.
pub fn instructions_to_binary<T>(instructions: T, labels: &Labels) -> Result<Vec<BinInstr>, Vec<AssemblyError>>
where
    T: IntoIterator,
    T::Item: Borrow<Instruction>
{
    let mut errors: Vec<AssemblyError> = Vec::new();

    let binary = instructions
        .into_iter()
        .enumerate()
        .map(|(address, instruction)| {
            if address == AddrRepr::MAX as usize + 1 {
                errors.push(AssemblyError::new(
                    format!("The maximum address limit of {} has been reached", AddrRepr::MAX),
                    None
                ));
            }

            let address = if address > AddrRepr::MAX as usize {
                Address(u10!(0))
            } else {
                Address(unsafe { u10::new_unchecked(address as u16) })
            };

            match instruction.borrow().to_binary(address, labels) {
                Ok(binary) => binary,
                Err(error) => {
                    errors.push(error);
                    0
                }
            }
        })
        .collect();

    if errors.is_empty() {
        Ok(binary)
    } else {
        Err(errors)
    }
}

/// Converts binary data to instructions.
pub fn binary_to_instructions<T>(binary: T) -> Vec<Instruction>
where
    T: IntoIterator,
    T::Item: Borrow<BinInstr>
{
    binary
        .into_iter()
        .map(|bin| Instruction::from_binary(*bin.borrow()))
        .collect()
}
