use std::{cell::RefCell, fmt, rc::Rc};

use crate::{
    error::MachineError,
    vm::{Value, stack::Stack, value::BytecodeFunction},
};

// #[derive(Debug, Clone, PartialEq)]
// pub enum UpvalueValue {
//     Open(usize),
//     Closed(Box<Value>),
// }

#[derive(Debug, PartialEq)]
pub enum Upvalue {
    Open(usize),
    Closed(Value),
}

#[derive(Clone, Debug, PartialEq)]
pub struct UpvalueRef(Rc<RefCell<Upvalue>>);

#[derive(Clone, PartialEq)]
pub struct ClosureValue {
    pub function: Rc<BytecodeFunction>,
    pub upvalues: Vec<UpvalueRef>,
}

impl UpvalueRef {
    pub fn open(sp: usize) -> Self {
        Self(Rc::new(RefCell::new(Upvalue::Open(sp))))
    }

    pub fn read(&self, stack: &Stack<Value>) -> Result<Value, MachineError> {
        match &*self.0.borrow() {
            &Upvalue::Open(sp) => stack
                .get(sp)
                .cloned()
                .ok_or(MachineError::UndefinedUpvalueReference),
            Upvalue::Closed(value) => Ok(value.clone()),
        }
    }

    pub fn write(&self, stack: &mut Stack<Value>, value: Value) -> Result<(), MachineError> {
        match &mut *self.0.borrow_mut() {
            &mut Upvalue::Open(sp) => {
                let slot = stack
                    .get_mut(sp)
                    .ok_or(MachineError::UndefinedUpvalueReference)?;
                *slot = value;
            }
            Upvalue::Closed(slot) => *slot = value,
        }
        Ok(())
    }

    pub fn close(&self, value: Value) -> Upvalue {
        self.0.replace(Upvalue::Closed(value))
    }
}

impl ClosureValue {
    pub fn instruction_byte(&self, address: usize) -> Option<u8> {
        self.function.instructions.get(address).copied()
    }
}

impl fmt::Debug for ClosureValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ClosureValue")
            .field("function", &self.function)
            .finish_non_exhaustive()
    }
}

impl fmt::Display for ClosureValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<closure {}@{:p}>", self.function.name(), self.function)
    }
}
