//! Internal data structures for running the code
use std::{
    any::TypeId,
    ops::{Deref, DerefMut},
    rc::Rc,
};

use gc_arena::{Collect, Gc};
use lasso::{Rodeo, Spur};
use magus_vals::PrimitiveData;

/// A Box for [`PrimitiveData`]
pub struct PrimitiveBox {
    /// Marks the original type that this box was created from
    inner_type: TypeId,
    inner: Box<dyn PrimitiveData>,
}

impl std::fmt::Debug for PrimitiveBox {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(datum) = self.as_datum() {
            _ = datum;
            // The debug representation is the print of the datum
            todo!()
        } else {
            write!(
                f,
                "#<{}>",
                self.inner
                    .as_debug()
                    .map(|d| format!("{:?}", d))
                    .unwrap_or_else(|| format!("primitive {}", self.inner.type_name()))
            )
        }
    }
}

impl Deref for PrimitiveBox {
    type Target = dyn PrimitiveData;
    fn deref(&self) -> &Self::Target {
        &*self.inner
    }
}

impl DerefMut for PrimitiveBox {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut *self.inner
    }
}

/// The Value that a VM/interpreter can manipulate
// TODO Change Debug to output datum representation
#[derive(Debug, Collect, Clone)]
#[collect(no_drop)]
#[allow(missing_docs)]
pub enum Primitive<'gc> {
    // The primary (possibly-)cyclical datastructures
    List(Gc<'gc, ConsCell<'gc>>),
    Vector(Vec<Gc<'gc, Primitive<'gc>>>),
    // good ol' vector of bytes
    // This is meant to primarily be a mutatable type
    // ergo Vec
    Bytevector(Vec<u8>),
    // TODO allow exact operations on numbers
    // For now, it's just integer = exact, and float = inexact
    Integer(i64),
    Float(f64),
    // Generally strings are immutable
    String(Rc<str>),
    // A symbol is a reference to the symbol table,
    // all symbols that are the same string take on the same index
    Symbol(SymbolId),
    Bool(bool),
    Data(Gc<'gc, PrimitiveBox>),
}

impl<'gc> Primitive<'gc> {
    /// Copy the value of this primitive to a [`typed_arena::Arena`]
    ///
    /// We use an arena to maintain cycles.
    pub fn copy_to_arena<'a>(
        &self,
        arena: &'a typed_arena::Arena<Primitive<'a>>,
    ) -> &'a mut FreePrimitive<'a> {
        _ = arena;
        todo!()
    }
}

/// A VM value not attached to a particular VM instance
#[derive(Debug, Clone)]
#[allow(missing_docs)]
pub enum FreePrimitive<'a> {
    List(FreeConsCell<'a>),
    Vector(Vec<FreePrimitive<'a>>),
    Bytevector(Vec<u8>),
    Integer(i64),
    Float(f64),
    String(Rc<str>),
    // As we don't know the target symbol table,
    // externalized symbols are represented by their symbol text
    Symbol(Rc<str>),
    Bool(bool),
    // TODO How can users *extract* what's in a PrimitiveBox.
    // - the type they want to extract as *must* implement Clone to get it out of the box
    Data(Rc<PrimitiveBox>),
}

/// A [`ListCell`] that is not attached to a VM instance
#[derive(Debug, Clone)]
pub struct FreeConsCell<'a> {
    /// car of a cons cell
    pub car: Option<&'a FreePrimitive<'a>>,
    /// cdr of a cons cell
    ///
    /// a "list" is when this points to another list or is None
    pub cdr: Option<&'a FreePrimitive<'a>>,
}

// TODO Change Debug to output datum representation
// Null -> ()
// Leaf(prim) -> <Debug of prim>
// Cons(car, cdr) -> (<Debug of car> <spliced Debug of cdr>)
// We might have a special "DebugCdr" struct just to handle the debug printing of cdr
// We will *definitely* need a helper "CycleIter" that can iterate through (possibly)
// cyclical datstructures
/// Representation of a Scheme list cell
#[derive(Debug, Collect, Clone)]
#[collect(no_drop)]
pub struct ConsCell<'gc> {
    car: Option<Gc<'gc, Primitive<'gc>>>,
    /// This cell is a list if this points to another list or is None (unset)
    cdr: Option<Gc<'gc, Primitive<'gc>>>,
}

impl Default for ConsCell<'_> {
    /// Create an empty list cell (Scheme `'()`)
    fn default() -> Self {
        Self {
            car: None,
            cdr: None,
        }
    }
}

impl<'gc> ConsCell<'gc> {
    /// Checks if this cell represents an empty list.
    pub fn is_empty_list(&self) -> bool {
        self.car.is_none() && self.cdr.is_none()
    }

    /// Checks if this cell is a list
    pub fn is_list(&self) -> bool {
        // We have 2 ways of representing an "empty list" for Scheme
        // - our cdr is pointed to None
        // - our car and cdr are None (if this is so, *we are the empty list*
        // and should technically not be considered for the "pair?" predicate)
        self.cdr
            .as_ref()
            .is_some_and(|cdr| matches!(cdr.as_ref(), Primitive::List(_)))
            || self.cdr.is_none()
    }
}

// NOTE We just use lasso types here
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Collect)]
#[collect(require_static)]
/// A lightweight representation of a symbol from a given symbol table
pub struct SymbolId(Spur);
/// A wrapper around a [`Rodeo`]
#[derive(Default)]
pub struct SymbolTable(Rodeo);

impl SymbolTable {
    /// If a valid symbol, get the id
    pub fn id<S: AsRef<str>>(&self, source: S) -> Option<SymbolId> {
        self.0.get(source).map(SymbolId)
    }

    /// If a valid symbol, get the id, otherwise, create a new id referencing the symbol
    pub fn id_mut<S: AsRef<str>>(&mut self, source: S) -> SymbolId {
        SymbolId(self.0.get_or_intern(source))
    }

    /// Tries to access the underlying string for a given [`SymbolId`]
    ///
    /// Fails if from the wrong table
    pub fn symbol(&self, id: SymbolId) -> Option<&str> {
        self.0.try_resolve(&id.0)
    }
}

// TODO Create constant table (can impl Collect using require_static, as it's only meant)
// for statically determinable data

// TODO Create "stack frame" struct

// TODO handle ports (they must be able to write/read from garbage-collected bytevectors)