use crate::*;

use SemanticTokenVariant as SemVar;


pub fn generate_bytecode(semantic_tokens: &mut [SemanticToken]) -> Vec<u8> {
    let generator = BytecodeGenerator::from_semantic_tokens(semantic_tokens);
    generator.generate()
}


/// Translate semantic tokens into bytecode.
struct BytecodeGenerator<'a> {
    semantic_tokens: &'a mut [SemanticToken],
    block_stack: Vec<usize>,
    bytecode: Vec<u8>,
    /// (address in bytecode, label definition token index)
    label_references: Vec<(usize, usize)>,
}

impl<'a> BytecodeGenerator<'a> {
    pub fn from_semantic_tokens(semantic_tokens: &'a mut [SemanticToken]) -> Self {
        Self {
            semantic_tokens,
            block_stack: Vec::new(),
            bytecode: Vec::new(),
            label_references: Vec::new(),
        }
    }

    pub fn generate(mut self) -> Vec<u8> {
        for i in 0..self.semantic_tokens.len() {
            let address = self.bytecode.len();
            self.generate_bytecode_for_token(i, None);
            self.semantic_tokens[i].bytecode = BytecodeSpan {
                bytes: self.bytecode[address..].to_vec(),
                location: BytecodeLocation {
                    address,
                    length: self.bytecode.len().saturating_sub(address),
                }
            };
        }

        // Replace blank label references in bytecode with real label addresses.
        // The layer of indirection is necessary because the iteration borrows
        // self immutably.
        let mut insertions: Vec<(usize, u16)> = Vec::new();
        for (bytecode_address, token_pointer) in &self.label_references {
            let label_token = &self.semantic_tokens[*token_pointer];
            // TODO: If greater than u16, print a warning.
            let address_value = label_token.bytecode.location.address as u16;
            insertions.push((*bytecode_address, address_value));
        }
        for (bytecode_address, address_value) in insertions {
            self.replace_address_in_bytecode(bytecode_address, address_value);
        }

        // Strip trailing null bytes from the bytecode.
        let mut length = self.bytecode.len();
        for (i, byte) in self.bytecode.iter().enumerate().rev() {
            match *byte == 0 {
                true => length = i,
                false => break,
            };
        }
        self.bytecode.truncate(length);

        return self.bytecode;
    }

    fn generate_bytecode_for_token(&mut self, pointer: usize, macro_pointer: Option<usize>) {
        macro_rules! push_byte {
            ($byte:expr) => { self.bytecode.push($byte) }; }
        macro_rules! push_double {
            ($double:expr) => { self.bytecode.extend_from_slice(&$double.to_be_bytes()) }; }
        macro_rules! pad {
            ($len:expr) => { for _ in 0..$len { push_byte!(0); } } }

        let semantic_token = if let Some(macro_pointer) = macro_pointer {
            let macro_definition = &self.semantic_tokens[macro_pointer];
            if let SemVar::MacroDefinition(def) = &macro_definition.variant {
                &def.body_tokens[pointer]
            } else { unreachable!() }
        } else {
                &self.semantic_tokens[pointer]
        };
        match &semantic_token.variant {
            SemVar::MacroInvocation(pointer) => {
                let macro_definition = &self.semantic_tokens[*pointer];
                if let SemVar::MacroDefinition(def) = &macro_definition.variant {
                    let length = def.body_tokens.len();
                    let macro_pointer = Some(*pointer);
                    for body_pointer in 0..length {
                        // Recurse, generate bytecode for each macro body token.
                        self.generate_bytecode_for_token(body_pointer, macro_pointer);
                    }
                } else { unreachable!() }
            }
            SemVar::Literal(value) => match value {
                Value::Byte(value) => push_byte!(*value),
                Value::Double(value) => push_double!(value),
            }
            SemVar::Padding(value) => match value {
                Value::Byte(value) => pad!(*value),
                Value::Double(value) => pad!(*value),
            }
            SemVar::Instruction(instr) => push_byte!(instr.value),
            SemVar::String(bytes) => self.bytecode.extend_from_slice(&bytes),
            SemVar::LabelReference(pointer) => {
                self.label_references.push((self.bytecode.len(), *pointer));
                push_double!(0u16);
            }
            SemVar::BlockOpen(_) => {
                self.block_stack.push(self.bytecode.len());
                push_double!(0u16);
            }
            SemVar::BlockClose(_) => {
                let bytecode_address = self.block_stack.pop().unwrap();
                // TODO: If greater than u16, print a warning.
                let address_value = self.bytecode.len() as u16;
                self.replace_address_in_bytecode(bytecode_address, address_value);
            }
            _ => (),
        };
    }

    fn replace_address_in_bytecode(&mut self, bytecode_address: usize, address_value: u16) {
        let range = bytecode_address..bytecode_address+2;
        self.bytecode[range].clone_from_slice(&address_value.to_be_bytes());
    }
}