use crate::compile::{
    block::{Compile, CompileContext, CompileValue},
    error::CompileError,
    instruction::Emitted,
    syntax::{CondExpression, IfExpression, LoopExpression, PrognExpression, WhileExpression},
};

impl Compile for WhileExpression {
    fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
        let label_entry = cx.new_label();
        let label_exit = cx.new_label();

        // Loop condition
        cx.adjust_label(label_entry);
        let condition_value = self.condition.compile(cx)?;
        cx.push(condition_value)?;
        cx.emit(Emitted::Branch(label_exit));

        cx.enter_loop(label_entry, label_exit);
        let body_value = self.body.compile(cx)?;
        cx.discard(body_value);
        cx.leave_loop();
        cx.emit(Emitted::Jump(label_entry));
        cx.adjust_label(label_exit);

        Ok(CompileValue::Nil)
    }
}

impl Compile for LoopExpression {
    fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
        let label_entry = cx.new_label();
        let label_exit = cx.new_label();

        cx.adjust_label(label_entry);
        cx.enter_loop(label_entry, label_exit);
        let body_value = self.body.compile(cx)?;
        cx.discard(body_value);
        cx.leave_loop();
        cx.emit(Emitted::Jump(label_entry));
        cx.adjust_label(label_exit);
        Ok(CompileValue::Nil)
    }
}

impl Compile for IfExpression {
    fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
        let label_false = cx.new_label();
        let label_end = cx.new_label();

        // Emit condition
        let condition_value = self.condition.compile(cx)?;
        cx.push(condition_value)?;
        cx.emit(Emitted::Branch(label_false));

        // True branch
        let true_value = self.if_true.compile(cx)?;
        cx.push(true_value)?;
        cx.emit(Emitted::Jump(label_end));

        // False branch
        cx.adjust_label(label_false);
        let false_value = match self.if_false.as_ref() {
            Some(expr) => expr.compile(cx)?,
            None => CompileValue::Nil,
        };
        cx.push(false_value)?;

        cx.adjust_label(label_end);

        Ok(CompileValue::Stack)
    }
}

impl Compile for CondExpression {
    fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
        let label_end = cx.new_label();

        for condition in self.arms.iter() {
            let label_otherwise = cx.new_label();
            let condition_value = condition.condition.compile(cx)?;
            cx.push(condition_value)?;
            cx.emit(Emitted::Branch(label_otherwise));
            // Condition true
            let then_value = condition.then.compile(cx)?;
            cx.push(then_value)?;
            cx.emit(Emitted::Jump(label_end));
            // Condition false
            cx.adjust_label(label_otherwise);
        }

        let otherwise_value = if let Some(otherwise_arm) = self.otherwise_arm.as_ref() {
            otherwise_arm.compile(cx)?
        } else {
            CompileValue::Nil
        };
        cx.push(otherwise_value)?;
        cx.adjust_label(label_end);

        Ok(CompileValue::Stack)
    }
}

impl Compile for PrognExpression {
    fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
        let value = self.body.compile(cx)?;
        cx.discard(value);
        Ok(CompileValue::Nil)
    }
}

pub(super) fn compile_break(cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
    cx.emit_break()?;
    Ok(CompileValue::Nil)
}
pub(super) fn compile_continue(cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
    cx.emit_continue()?;
    Ok(CompileValue::Nil)
}
