rmquickjs 0.0.1

High-level binding API for MicroQuickJS
use core::{ffi::CStr, ptr::null_mut};

use alloc::{
    boxed::Box,
    string::{String, ToString},
};
use rmquickjs_sys::{
    JSCStringBuf, JSGCRef, JSObjectClassEnum_JS_CLASS_ARRAY,
    JSObjectClassEnum_JS_CLASS_FLOAT32_ARRAY, JSObjectClassEnum_JS_CLASS_FLOAT64_ARRAY,
    JSObjectClassEnum_JS_CLASS_INT8_ARRAY, JSObjectClassEnum_JS_CLASS_INT16_ARRAY,
    JSObjectClassEnum_JS_CLASS_INT32_ARRAY, JSObjectClassEnum_JS_CLASS_TYPED_ARRAY,
    JSObjectClassEnum_JS_CLASS_UINT8_ARRAY, JSObjectClassEnum_JS_CLASS_UINT16_ARRAY,
    JSObjectClassEnum_JS_CLASS_UINT32_ARRAY, JSValue,
};

use crate::{Array, Context, Function, Object};

#[derive(Debug, Clone, Copy)]
pub struct Value(JSValue);

impl Value {
    pub fn from_raw(value: JSValue) -> Self {
        Self(value)
    }

    pub fn into_raw(self) -> JSValue {
        self.0
    }

    pub fn null() -> Self {
        Self(rmquickjs_sys::JS_NULL())
    }

    pub fn undefined() -> Self {
        Self(rmquickjs_sys::JS_UNDEFINED())
    }

    pub fn exception() -> Self {
        Self(rmquickjs_sys::JS_EXCEPTION())
    }

    pub const fn is_exception(&self) -> bool {
        self.0 == rmquickjs_sys::JS_EXCEPTION()
    }

    pub const fn is_null(&self) -> bool {
        self.0 == rmquickjs_sys::JS_NULL()
    }

    pub const fn is_undefined(&self) -> bool {
        self.0 == rmquickjs_sys::JS_UNDEFINED()
    }

    pub const fn is_true(&self) -> bool {
        self.0 == rmquickjs_sys::JS_TRUE()
    }

    pub const fn is_false(&self) -> bool {
        self.0 == rmquickjs_sys::JS_FALSE()
    }

    pub fn is_bool(&self) -> bool {
        rmquickjs_sys::JS_IsBool(self.0)
    }

    pub const fn is_int(&self) -> bool {
        rmquickjs_sys::JS_IsInt(self.0)
    }

    pub const fn is_ptr(&self) -> bool {
        rmquickjs_sys::JS_IsPtr(self.0)
    }

    pub fn is_number(&self, ctx: &Context) -> bool {
        unsafe { rmquickjs_sys::JS_IsNumber(ctx.as_ptr(), self.0) == 1 }
    }

    pub fn is_string(&self, ctx: &Context) -> bool {
        unsafe { rmquickjs_sys::JS_IsString(ctx.as_ptr(), self.0) == 1 }
    }

    pub fn is_function(&self, ctx: &Context) -> bool {
        unsafe { rmquickjs_sys::JS_IsFunction(ctx.as_ptr(), self.0) == 1 }
    }

    pub fn is_object(&self, ctx: &Context) -> bool {
        unsafe { rmquickjs_sys::JS_GetClassID(ctx.as_ptr(), self.0) != -1 }
    }

    #[allow(non_upper_case_globals)]
    pub fn is_array(&self, ctx: &Context) -> bool {
        unsafe {
            let class_id = rmquickjs_sys::JS_GetClassID(ctx.as_ptr(), self.0);
            if class_id == -1 {
                return false;
            }

            matches!(
                class_id as u32,
                JSObjectClassEnum_JS_CLASS_ARRAY
                    | JSObjectClassEnum_JS_CLASS_INT8_ARRAY
                    | JSObjectClassEnum_JS_CLASS_INT16_ARRAY
                    | JSObjectClassEnum_JS_CLASS_INT32_ARRAY
                    | JSObjectClassEnum_JS_CLASS_UINT8_ARRAY
                    | JSObjectClassEnum_JS_CLASS_UINT16_ARRAY
                    | JSObjectClassEnum_JS_CLASS_UINT32_ARRAY
                    | JSObjectClassEnum_JS_CLASS_FLOAT32_ARRAY
                    | JSObjectClassEnum_JS_CLASS_FLOAT64_ARRAY
                    | JSObjectClassEnum_JS_CLASS_TYPED_ARRAY
            )
        }
    }

    pub fn is_error(&self, ctx: &Context) -> bool {
        unsafe { rmquickjs_sys::JS_IsError(ctx.as_ptr(), self.0) == 1 }
    }

    pub fn to_number(&self, ctx: &Context) -> Option<f64> {
        let mut result = 0.0;
        unsafe {
            if rmquickjs_sys::JS_ToNumber(ctx.as_ptr(), &mut result, self.0) == 0 {
                Some(result)
            } else {
                None
            }
        }
    }

    pub fn to_i32(&self, ctx: &Context) -> Option<i32> {
        let mut result = 0;
        unsafe {
            if rmquickjs_sys::JS_ToInt32(ctx.as_ptr(), &mut result, self.0) == 0 {
                Some(result)
            } else {
                None
            }
        }
    }

    pub fn to_i32_sat(&self, ctx: &Context) -> Option<i32> {
        let mut result = 0;
        unsafe {
            if rmquickjs_sys::JS_ToInt32Sat(ctx.as_ptr(), &mut result, self.0) == 0 {
                Some(result)
            } else {
                None
            }
        }
    }

    pub fn to_u32(&self, ctx: &Context) -> Option<u32> {
        let mut result = 0;
        unsafe {
            if rmquickjs_sys::JS_ToUint32(ctx.as_ptr(), &mut result, self.0) == 0 {
                Some(result)
            } else {
                None
            }
        }
    }

    pub fn to_string(&self, ctx: &Context) -> String {
        unsafe {
            let mut buf = JSCStringBuf { buf: [0; 5] };
            let mut len = 0;
            let str = rmquickjs_sys::JS_ToCStringLen(ctx.as_ptr(), &mut len, self.0, &mut buf);
            CStr::from_ptr(str).to_string_lossy().to_string()
        }
    }

    pub fn to_object<'a>(&self, ctx: &'a Context) -> Option<Object<'a>> {
        if self.is_object(ctx) {
            unsafe {
                let mut gc_ref = Box::new(JSGCRef {
                    val: self.0,
                    prev: null_mut(),
                });

                let slot = rmquickjs_sys::JS_AddGCRef(ctx.as_ptr(), gc_ref.as_mut());
                *slot = self.0;

                Some(Object::new(gc_ref, ctx))
            }
        } else {
            None
        }
    }

    pub fn to_array<'a>(&self, ctx: &'a Context) -> Option<Array<'a>> {
        if self.is_array(ctx) {
            unsafe {
                let mut gc_ref = Box::new(JSGCRef {
                    val: self.0,
                    prev: null_mut(),
                });

                let slot = rmquickjs_sys::JS_AddGCRef(ctx.as_ptr(), gc_ref.as_mut());
                *slot = self.0;

                Some(Array::new(gc_ref, ctx))
            }
        } else {
            None
        }
    }

    pub fn to_function<'a>(&self, ctx: &'a Context) -> Option<Function<'a>> {
        if self.is_function(ctx) {
            unsafe {
                let mut gc_ref = Box::new(JSGCRef {
                    val: self.0,
                    prev: null_mut(),
                });

                let slot = rmquickjs_sys::JS_AddGCRef(ctx.as_ptr(), gc_ref.as_mut());
                *slot = self.0;

                Some(Function::new(gc_ref, ctx))
            }
        } else {
            None
        }
    }
}