use crate::*;

use indexmap::IndexMap;


/// The entire semantic program, ready to generate bytecode.
pub struct SemanticProgram {
    pub macro_definitions: IndexMap<String, MacroDefinition>,
    pub label_definitions: IndexMap<String, LabelDefinition>,
    pub body: Vec<SemanticToken>,
}

/// A symbol definition.
pub struct MacroDefinition {
    pub source: SourceSpan,
    pub arguments: Vec<ArgumentDefinition>,
    pub value: Value,
    pub errors: Vec<SemanticParseError>,
}

pub struct ArgumentDefinition {
    pub name: String,
    pub source: SourceSpan,
    pub variant: ArgumentVariant,
}

#[derive(PartialEq, Clone, Copy, Debug)]
pub enum ArgumentVariant {
    Integer,
    Block,
}

pub struct ArgumentInvocation {
    pub source: SourceSpan,
    pub value: Value,
}

pub enum Value {
    Integer(Integer),
    Block(Vec<SemanticToken>),
    Invocation(Invocation),
}

pub enum Integer {
    Literal(TrackedInteger),
    String(TrackedString),
    Expression(Expression),
    LabelReference(Tracked<String>),
}

pub enum SemanticToken {
    Word(PackedBinaryLiteral),
    Invocation(Invocation),
    LabelDefinition(LabelDefinition),
    PinnedAddress(PinnedAddress),
    Error(SemanticParseError),
}

pub struct Invocation {
    pub name: String,
    pub source: SourceSpan,
    pub arguments: Vec<ArgumentInvocation>,
    pub errors: Vec<SemanticParseError>,
}

#[derive(Clone)]
pub struct LabelDefinition {
    pub source: SourceSpan,
    pub name: String,
}

#[derive(Clone)]
pub struct PinnedAddress {
    pub source: SourceSpan,
    pub address: usize,
}

pub struct SemanticParseError {
    pub source: SourceSpan,
    pub variant: SemanticParseErrorVariant,
}

pub enum SemanticParseErrorVariant {
    UnterminatedMacroDefinition(String),
    UnterminatedBlock,
    InvalidToken,
}


impl std::fmt::Display for ArgumentVariant {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
        match self {
            ArgumentVariant::Integer => write!(f, "integer"),
            ArgumentVariant::Block => write!(f, "block"),
        }
    }
}

// ------------------------------------------------------------------------ //

macro_rules! indent {
    ($indent:expr => $($tokens:tt)*) => {{
        for _ in 0..$indent { print!("  "); }
        println!($($tokens)*);
    }};
}

impl SemanticProgram {
    pub fn print_definitions(&self) {
        for (name, definition) in &self.macro_definitions {
            let variant = match &definition.value {
                Value::Integer(_) => "INTEGER",
                Value::Block(_) => "BLOCK",
                Value::Invocation(_) => "INVOCATION",
            };
            println!("DEFINE {variant} '{name}'");
            for argument in &definition.arguments {
                self.print_argument_definition(argument);
            }
            match &definition.value {
                Value::Integer(integer) =>
                    self.print_integer(1, integer),
                Value::Block(block) =>
                    self.print_block(1, block),
                Value::Invocation(invocation) =>
                    indent!(1 => "INVOCATION '{}'", invocation.name),
            };
            println!();
        }

        println!("LABELS");
        for (name, _) in &self.label_definitions {
            println!("  @{name}");
        }
        println!();

        self.print_block(0, &self.body);
    }

    fn print_argument_definition(&self, argument: &ArgumentDefinition) {
        let variant = match argument.variant {
            ArgumentVariant::Integer => "INTEGER",
            ArgumentVariant::Block => "BLOCK",
        };
        println!("  ARGUMENT {variant} '{}'", argument.name);
    }

    fn print_integer(&self, indent: usize, integer: &Integer) {
        match &integer {
            Integer::Literal(value) =>
                indent!(indent => "LITERAL {value}"),
            Integer::Expression(expr) =>
                indent!(indent => "EXPRESSION [{expr:?}]"),
            Integer::String(string) =>
                indent!(indent => "STRING '{string}'"),
            Integer::LabelReference(name) =>
                indent!(indent => "LABEL REFERENCE '{name}'"),
        }
    }

    fn print_block(&self, indent: usize, block: &[SemanticToken]) {
        indent!(indent => "BLOCK");
        for semantic_token in block {
            match &semantic_token {
                SemanticToken::Word(word) =>
                    indent!(indent+1 => "WORD #{word}"),
                SemanticToken::Invocation(invocation) =>
                    self.print_invocation(indent+1, invocation),
                SemanticToken::LabelDefinition(definition) =>
                    indent!(indent+1 => "LABEL DEFINITION @{}", definition.name),
                SemanticToken::PinnedAddress(addr) =>
                    indent!(indent+1 => "PINNED ADDRESS {}", addr.address),
                SemanticToken::Error(_) =>
                    indent!(indent+1 => "ERROR"),
            }
        }
    }

    fn print_invocation(&self, indent: usize, invocation: &Invocation) {
        indent!(indent => "INVOCATION '{}'", invocation.name);
        for argument in &invocation.arguments {
            match &argument.value {
                Value::Integer(integer) =>
                    self.print_integer(indent+1, integer),
                Value::Block(block) =>
                    self.print_block(indent+1, block),
                Value::Invocation(invocation) =>
                    self.print_invocation(indent+1, invocation),
            };
        }
    }
}