use crate::*;

use std::collections::HashMap;


/// Doesn't truncate trailing null bytes.
pub fn generate_bytecode(semantic: &Program) -> AssembledProgram {
    let mut generator = BytecodeGenerator::new(&semantic.definitions);
    generator.parse(&semantic.tokens, false);
    generator.fill_slots();
    let mut symbols = Vec::new();
    for (name, information) in generator.labels {
        let source = semantic.definitions.get(&name).unwrap().source.clone();
        let address = information.address;
        symbols.push(AssembledSymbol { name, address, source });
    }
    AssembledProgram {
        bytecode: generator.bytecode,
        symbols,
    }
}


pub struct BytecodeGenerator<'a> {
    definitions: &'a HashMap<String, Tracked<Definition>>,
    labels: HashMap<String, LabelInformation>,
    stack: Vec<usize>,
    bytecode: Vec<u8>,
}

struct LabelInformation {
    address: usize,
    slots: Vec<usize>,
}

impl<'a> BytecodeGenerator<'a> {
    pub fn new(definitions: &'a HashMap<String, Tracked<Definition>>) -> Self {
        let mut labels = HashMap::new();
        for (name, definition) in definitions {
            if let DefinitionKind::LabelDefinition = definition.kind {
                labels.insert(name, LabelInformation { address: 0, slots: Vec::new() });
            }
        }
        Self {
            definitions,
            labels: HashMap::new(),
            stack: Vec::new(),
            bytecode: Vec::new(),
        }
    }

    pub fn parse(&mut self, tokens: &[Tracked<SemanticToken>], in_macro: bool) {
        macro_rules! byte {
            ($byte:expr) => {
                self.bytecode.push($byte)
            };
        }
        macro_rules! double {
            ($double:expr) => {{
                let [high, low] = u16::to_be_bytes($double);
                self.bytecode.push(high);
                self.bytecode.push(low);
            }};
        }

        for token in tokens {
            let i = self.bytecode.len();
            match &token.value {
                SemanticToken::Comment(_) => (),

                SemanticToken::LabelDefinition(name) => if in_macro {
                    unreachable!("Uncaught label definition in macro");
                } else {
                    let information = self.labels.get_mut(name).unwrap();
                    information.address = i;
                }
                SemanticToken::MacroDefinition{ .. } => if in_macro {
                    unreachable!("Uncaught macro definition in macro");
                }

                SemanticToken::RawValue(value) => match value {
                    Value::Byte(byte) => byte!(*byte),
                    Value::Double(double) => double!(*double),
                }
                SemanticToken::Instruction(instruction) => {
                    byte!(instruction.value)
                }
                SemanticToken::Invocation(name) => {
                    if let Some(definition) = self.definitions.get(name) {
                        match &definition.kind {
                            DefinitionKind::MacroDefinition(body) => {
                                self.parse(body, true);
                            }
                            DefinitionKind::LabelDefinition => {
                                let information = self.labels.get_mut(name).unwrap();
                                information.slots.push(i);
                                double!(0);
                            }
                        }
                    } else {
                        unreachable!("Uncaught undefined symbol '{name}'");
                    }
                }

                SemanticToken::Padding(value) => {
                    self.bytecode.resize(i + usize::from(value), 0);
                },
                SemanticToken::String(bytes) => {
                    self.bytecode.extend_from_slice(bytes)
                },

                SemanticToken::BlockOpen(_) => {
                    self.stack.push(i);
                    double!(0);
                }
                SemanticToken::BlockClose(_) => {
                    let Some(addr) = self.stack.pop() else {
                        unreachable!("Uncaught unmatched block terminator");
                    };
                    let [high, low] = (addr as u16).to_be_bytes();
                    self.bytecode[addr] = high;
                    self.bytecode[addr+1] = low;
                }
            }
        }
    }

    pub fn fill_slots(&mut self) {
        for information in self.labels.values() {
            let [high, low] = (information.address as u16).to_be_bytes();
            for addr in &information.slots {
                self.bytecode[*addr] = high;
                self.bytecode[*addr + 1] = low;
            }
        }
    }
}