use quick_js::Context;
use quick_js::JsValue;
use quick_js::console::Level;
use quick_js::console::ConsoleBackend;
use serde::{ Serialize, Deserialize };
use rust_util::XResult;
use crate::sig::*;

#[derive(Serialize, Deserialize)]
pub struct QuickJSPack {
    hash: String, // hash in hex == HEX(SHA256(self.script))
    script: String,
    sig: SignedMessage, // sig.msg = SHA256(self.script)
}

pub struct QuickJSContext {
    context: Context,
}

impl QuickJSContext {
    pub fn new() -> XResult<Self> {
        Ok(Self{
            context: build_default_context()?,
        })
    }

    pub fn init(&self, js: &str) -> XResult<JsValue> {
        self.context.eval(js).map_err(|e| e.into())
    }

    // pub fn run_js<T>(&self, p: &T) -> XResult<JsValue> where T: ?Sized + Serialize {
    //     let p_json = match serde_json::to_string(p) {
    //         Err(e) => return Err(e.into()),
    //         Ok(p) => p,
    //     };
    //     self.context.eval(&format!("main({})", p_json)).map_err(|e| e.into())
    // }

    pub fn call_fn(&self, fun: &str, params: &str) -> XResult<JsValue> {
        if !params.trim().starts_with("[") {
            return Err(rust_util::new_box_error("Params is not JSON array!"));
        }
        let f: String = fun.chars().map(|c| if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' {
            c
        } else {
            '_'
        }).collect();

        if let Err(e) = json::parse(params) {
            return Err(rust_util::new_box_error(&format!("Params is not valid JSON array: {}", e)));
        }
        self.context.eval(&format!("__PUBLIC_{}.apply(null, {})", f, params)).map_err(|e| e.into())
    }
}

#[derive(Serialize, Deserialize)]
struct EmptyObject {}

struct ConsoleBackendImpl;
impl ConsoleBackend for ConsoleBackendImpl {
    fn log(&self, level: Level, values: Vec<JsValue>) {
        println!("[{:>5}] - {:?}", format!("{:?}", level).to_uppercase(), values);
    }
}

fn build_default_context() -> XResult<Context> {
    Context::builder().memory_limit(16 * 1024 * 1024)
                      .console(ConsoleBackendImpl{})
                      .build()
                      .map_err(|e| e.into())
}
