/********************************************************************************
 * Copyright (c) 2020 Equo
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Guillermo Zunino, Equo - initial implementation
 ********************************************************************************/
extern crate chromium;

#[cfg(target_os = "linux")]
extern crate x11;
#[cfg(unix)]
extern crate nix;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;

use chromium::cef;
use chromium::utils;
use chromium::socket;

mod app;
#[cfg(target_os = "linux")]
mod gtk;

use std::os::raw::{c_char, c_int, c_void};
#[cfg(unix)]
use std::collections::HashMap;

#[cfg(target_os = "linux")]
unsafe extern fn xerror_handler_impl(_: *mut x11::xlib::Display, _event: *mut x11::xlib::XErrorEvent) -> c_int {
    //print!("X error received: ");
    //println!("type {}, serial {}, error_code {}, request_code {}, minor_code {}",
    //    (*event).type_, (*event).serial, (*event).error_code, (*event).request_code, (*event).minor_code);
    0
}
#[cfg(target_os = "linux")]
unsafe extern fn xioerror_handler_impl(_: *mut x11::xlib::Display) -> c_int {
    //println!("XUI error received");
    0
}

#[no_mangle]
pub extern fn cefswt_init(japp: *mut cef::cef_app_t, cefrust_path: *const c_char, cef_path: *const c_char, version: *const c_char, debug_port: c_int) {
    //println!("DLL init");
    assert_eq!(unsafe{(*japp).base.size}, std::mem::size_of::<cef::_cef_app_t>());
    //println!("app {:?}", japp);

    let cefrust_path = utils::str_from_c(cefrust_path);
    let cef_path = utils::str_from_c(cef_path);
    let version = utils::str_from_c(version);

    let main_args = utils::prepare_args();

    let cefrust_dir = std::path::Path::new(&cefrust_path);
    let cef_dir = std::path::Path::new(&cef_path);

    let subp = utils::subp_path(cefrust_dir, version);
    let subp_cef = utils::cef_string(&subp);

    if cef_path != cefrust_path {
        if cfg!(target_family = "windows") {
            set_env_var(cef_path, "PATH", ";");
        }
        if cfg!(target_os = "linux") {
            set_env_var(cef_path, "LD_LIBRARY_PATH", ":");
        }
        if cfg!(target_os = "macos") {
            set_env_var(cef_path, "DYLD_LIBRARY_PATH", ":")
        }
    }

    let resources_cef = if cfg!(target_os = "macos") {
        utils::cef_string(cef_dir.join("Chromium Embedded Framework.framework").join("Resources").to_str().unwrap())
    } else {
        utils::cef_string(cef_dir.to_str().unwrap())
    };
    let locales_cef = if cfg!(target_os = "macos") {
        utils::cef_string(cef_dir.join("Chromium Embedded Framework.framework").join("Resources").to_str().unwrap())
    } else {
        utils::cef_string(cef_dir.join("locales").to_str().unwrap())
    };
    let framework_dir_cef = if cfg!(target_os = "macos") {
        utils::cef_string(cef_dir.join("Chromium Embedded Framework.framework").to_str().unwrap())
    } else {
        utils::cef_string_empty()
    };

    let cache_dir_cef = utils::cef_string(cef_dir.parent().unwrap().parent().unwrap().join("cef_cache").to_str().unwrap());

    let logfile_cef = utils::cef_string(cef_dir.join("lib.log").to_str().unwrap());

    let settings = cef::_cef_settings_t {
        size: std::mem::size_of::<cef::_cef_settings_t>(),
        single_process: 0,
        no_sandbox: 1,
        browser_subprocess_path: subp_cef,
        framework_dir_path: framework_dir_cef,
        multi_threaded_message_loop: 0,
        external_message_pump: 1,
        windowless_rendering_enabled: 0,
        command_line_args_disabled: 0,
        cache_path: cache_dir_cef,
        user_data_path: utils::cef_string_empty(),
        persist_session_cookies: 1,
        persist_user_preferences: 1,
        user_agent: utils::cef_string_empty(),
        product_version: utils::cef_string_empty(),
        locale: utils::cef_string_empty(),
        log_file: logfile_cef,
        log_severity: cef::cef_log_severity_t::LOGSEVERITY_INFO,
        // log_severity: cef::cef_log_severity_t::LOGSEVERITY_VERBOSE,
        javascript_flags: utils::cef_string_empty(),
        resources_dir_path: resources_cef,
        locales_dir_path: locales_cef,
        pack_loading_disabled: 0,
        remote_debugging_port: debug_port,
        uncaught_exception_stack_size: 0,
        ignore_certificate_errors: 0,
        enable_net_security_expiration: 0,
        background_color: 0,
        accept_language_list: utils::cef_string_empty()
    };

    //println!("Calling cef_initialize");
    do_initialize(main_args, settings, japp);
    // println!("After Calling cef_initialize");
}

fn set_env_var(cef_path: &str, var: &str, sep: &str) {
    match std::env::var(var) {
        Ok(paths) => {
            let paths = format!("{}{}{}", cef_path, sep, paths);
            println!("{}: {}", var, paths);
            std::env::set_var(var, paths);
        },
        Err(e) => {
            println!("Couldn't read {} ({})", var, e);
            std::env::set_var(var, cef_path);
        },
    };
}

#[cfg(target_os = "linux")]
fn do_initialize(main_args: cef::_cef_main_args_t, settings: cef::_cef_settings_t, app_raw: *mut cef::_cef_app_t) {
    unsafe { x11::xlib::XSetErrorHandler(Option::Some(xerror_handler_impl)) };
    unsafe { x11::xlib::XSetIOErrorHandler(Option::Some(xioerror_handler_impl)) };

    let mut signal_handlers: HashMap<c_int, nix::sys::signal::SigAction> = HashMap::new();
    backup_signal_handlers(&mut signal_handlers);

    unsafe { cef::cef_initialize(&main_args, &settings, app_raw, std::ptr::null_mut()) };

    restore_signal_handlers(signal_handlers);
}

#[cfg(target_os = "macos")]
static EVENT_KEY: char = 'k';

#[cfg(target_os = "macos")]
fn do_initialize(main_args: cef::_cef_main_args_t, settings: cef::_cef_settings_t, app_raw: *mut cef::_cef_app_t) {
    let mut signal_handlers: HashMap<c_int, nix::sys::signal::SigAction> = HashMap::new();
    backup_signal_handlers(&mut signal_handlers);

    swizzle_send_event();

    unsafe { cef::cef_initialize(&main_args, &settings, &mut (*app_raw), std::ptr::null_mut()) };

    restore_signal_handlers(signal_handlers);
}

#[cfg(target_os = "macos")]
fn swizzle_send_event() {
    use std::ffi::CString;
    use objc::runtime::{BOOL, Class, Method, NO, YES, Object, Sel, self};
    use objc::{Encode, EncodeArguments, Encoding};
    use nix::libc::intptr_t;

    fn count_args(sel: Sel) -> usize {
        sel.name().chars().filter(|&c| c == ':').count()
    }

    fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
        let mut types = ret.as_str().to_owned();
        // First two arguments are always self and the selector
        types.push_str(<*mut Object>::encode().as_str());
        types.push_str(Sel::encode().as_str());
        types.extend(args.iter().map(|e| e.as_str()));
        CString::new(types).unwrap()
    }

    pub unsafe fn add_method<F>(cls: *mut Class, sel: Sel, func: F)
            where F: objc::declare::MethodImplementation<Callee=Object> {
        let encs = F::Args::encodings();
        let encs = encs.as_ref();
        let sel_args = count_args(sel);
        assert!(sel_args == encs.len(),
            "Selector accepts {} arguments, but function accepts {}",
            sel_args, encs.len(),
        );

        let types = method_type_encoding(&F::Ret::encode(), encs);
        let success = runtime::class_addMethod(cls, sel, func.imp(),
            types.as_ptr());
        assert!(success != NO, "Failed to add method {:?}", sel);
    }

    pub type Id = *mut runtime::Object;
    pub type AssociationPolicy = intptr_t;
    extern {
        pub fn objc_getAssociatedObject(object: Id, key: *const c_void) -> BOOL;
        pub fn objc_setAssociatedObject(object: Id,
                                    key: *const c_void,
                                    value: BOOL,
                                    policy: AssociationPolicy);
    }

    let cls_nm = CString::new("NSApplication").unwrap();
    let cls = unsafe { runtime::objc_getClass(cls_nm.as_ptr()) as *mut Class };
    assert!(!cls.is_null(), "null class");

    extern fn is_handling_sendevent(this: &mut Object, _cmd: Sel) -> BOOL {
        //println!("isHandlingSendEvent {:?}", this);
        let kp = &EVENT_KEY as *const _ as *const c_void;
        let is = unsafe { objc_getAssociatedObject(this, kp) };
        //println!("AssociatedObject: {:?}", is);
        is
    }
    unsafe { add_method(cls, sel!(isHandlingSendEvent), is_handling_sendevent as extern fn(&mut Object, Sel) -> BOOL) };

    extern fn set_handling_sendevent(this: &mut Object, _cmd: Sel, handling_sendevent: BOOL) {
        //println!("setHandlingSendEvent {:?} {:?}", this, handling_sendevent);
        let kp = &EVENT_KEY as *const _ as *const c_void;
        let policy_assign = 0;
        unsafe { objc_setAssociatedObject(this, kp, handling_sendevent, policy_assign) };
    }
    unsafe { add_method(cls, sel!(setHandlingSendEvent:), set_handling_sendevent as extern fn(&mut Object, Sel, BOOL)) };

    extern fn swizzled_sendevent(this: &mut Object, _cmd: Sel, event: Id) {
        //println!("swizzled_sendevent {:?}", this);
        unsafe {
            let handling: BOOL = msg_send![this, isHandlingSendEvent];
            msg_send![this, setHandlingSendEvent:YES];
            msg_send![this, _swizzled_sendEvent:event];
            msg_send![this, setHandlingSendEvent:handling];
        }
    }
    let sel_swizzled_sendevent = sel!(_swizzled_sendEvent:);
    unsafe { add_method(cls, sel_swizzled_sendevent, swizzled_sendevent as extern fn(&mut Object, Sel, Id)) };

    unsafe {
        let original = runtime::class_getInstanceMethod(cls, sel!(sendEvent:)) as *mut Method;
        let swizzled = runtime::class_getInstanceMethod(cls, sel_swizzled_sendevent) as *mut Method;
        runtime::method_exchangeImplementations(original, swizzled);
    }
}

#[cfg(target_os = "windows")]
fn do_initialize(main_args: cef::_cef_main_args_t, settings: cef::_cef_settings_t, app_raw: *mut cef::_cef_app_t) {
    unsafe { cef::cef_enable_highdpi_support() };
    unsafe { cef::cef_initialize(&main_args, &settings, &mut (*app_raw), std::ptr::null_mut()) };
}

#[cfg(unix)]
fn backup_signal_handlers(signal_handlers: &mut HashMap<c_int, nix::sys::signal::SigAction>) {
    use nix::sys::signal;
    let signals_to_restore = [signal::SIGHUP, signal::SIGINT, signal::SIGQUIT, signal::SIGILL,
        signal::SIGABRT, signal::SIGFPE, signal::SIGSEGV, signal::SIGALRM, signal::SIGTERM,
        signal::SIGCHLD, signal::SIGBUS, signal::SIGTRAP, signal::SIGPIPE];

    for signal in &signals_to_restore {
        let sig_action = signal::SigAction::new(signal::SigHandler::SigDfl,
                                          signal::SaFlags::empty(),
                                          signal::SigSet::empty());
        let oldsigact = unsafe { signal::sigaction(*signal, &sig_action) };
        //println!("backup signal {:?}:{:?}", signal, "oldsigact.ok()");
        signal_handlers.insert(*signal as c_int, oldsigact.unwrap());
    }
}

#[cfg(unix)]
fn restore_signal_handlers(signal_handlers: HashMap<c_int, nix::sys::signal::SigAction>) {
    use nix::sys::signal;
    for (signal, sigact) in signal_handlers {
        //println!("restore signal {:?}:{:?}", signal, "sigact");
        unsafe { signal::sigaction(std::mem::transmute(signal), &sigact).unwrap() };
    }
}

#[no_mangle]
pub extern fn cefswt_create_browser(hwnd: *mut c_void, url: *const c_char, client: &mut cef::_cef_client_t, w: c_int, h: c_int, js: c_int, bg: cef::cef_color_t) -> *const cef::cef_browser_t {
    assert_eq!((*client).base.size, std::mem::size_of::<cef::_cef_client_t>());

    // println!("hwnd: {:?}", hwnd);
    // (*client).on_process_message_received = Option::Some(on_process_message_received);

    let url = utils::str_from_c(url);
    // println!("url: {:?}", url);

    let browser = app::create_browser(hwnd, url, client, w, h, js, bg);
    // println!("after create browser");

    // let browser_host = get_browser_host(browser);
    // unsafe {
    //     (*browser_host).show_dev_tools.expect("no dev_tools")(browser_host, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut());
    // }
    browser
}

#[no_mangle]
pub extern fn cefswt_set_window_info_parent(window_info: *mut cef::_cef_window_info_t, client: *mut *mut cef::_cef_client_t, jclient: &mut cef::_cef_client_t, hwnd: *mut c_void, x: c_int, y: c_int, w: c_int, h: c_int) {
    unsafe {
        //println!("cefswt_set_window_info_parent {:?} {}", *window_info, hwnd);
        (*client) = jclient;
        app::set_window_parent(window_info, hwnd, x, y, w, h);
        //println!("after cefswt_set_window_info_parent {:?} {}", *window_info, hwnd);
    };
}

#[no_mangle]
pub extern fn cefswt_do_message_loop_work() -> c_int {
    let result = std::panic::catch_unwind(|| {
        unsafe { cef::cef_do_message_loop_work() };
    });
    match result {
        Ok(_) => 1,
        Err(_) => 0
    }
}

#[no_mangle]
pub extern fn cefswt_free(obj: *mut cef::cef_browser_t) {
    //println!("freeing {:?}", obj);
    unsafe {
        assert_eq!((*obj).base.size, std::mem::size_of::<cef::_cef_browser_t>());

        // let rls_fn = (*obj).base.release.expect("null release");
        // println!("call rls");
        // let refs = rls_fn(obj as *mut cef::_cef_base_ref_counted_t);
        // assert_eq!(refs, 1);
    }

    //println!("freed");
}

#[no_mangle]
pub extern fn cefswt_get_id(browser: *mut cef::cef_browser_t) -> c_int {
    unsafe {
        let get_id = (*browser).get_identifier.unwrap();
        get_id(browser)
    }
}

#[no_mangle]
pub extern fn cefswt_resized(browser: *mut cef::cef_browser_t, width: i32, height: i32) {
    //println!("Calling resized {}:{}", width, height);

    let browser_host = get_browser_host(browser);
    let get_window_handle_fn = unsafe { (*browser_host).get_window_handle.expect("no get_window_handle") };
    let win_handle = unsafe { get_window_handle_fn(browser_host) };
    do_resize(win_handle, width, height);
}

#[cfg(target_os = "linux")]
fn do_resize(win_handle: *mut c_void, width: i32, height: i32) {
    use x11::xlib;

    let xwindow = win_handle;
    let xdisplay = unsafe { cef::linux::cef_get_xdisplay() };
    let mut changes = xlib::XWindowChanges {
        x: 0,
        y: 0,
        width: width,
        height: height,
        border_width: 0,
        sibling: 0,
        stack_mode: 0
    };
    unsafe { xlib::XConfigureWindow(std::mem::transmute(xdisplay), xwindow as u64,
        (xlib::CWX | xlib::CWY | xlib::CWHeight | xlib::CWWidth) as u32, &mut changes) };
}

#[cfg(target_os = "macos")]
fn do_resize(_win_handle: *mut c_void, _: i32, _: i32) {
    // handled by cocoa
}

#[cfg(target_family = "windows")]
fn do_resize(win_handle: *mut c_void, width: i32, height: i32) {
    extern crate winapi;

    let x = 0;
    let y = 0;
    unsafe { winapi::um::winuser::SetWindowPos(win_handle as winapi::shared::windef::HWND,
        std::ptr::null_mut(), x, y, width, height, winapi::um::winuser::SWP_NOZORDER) };
}

#[no_mangle]
pub extern fn cefswt_close_browser(browser: *mut cef::cef_browser_t, force: c_int) {
    let browser_host = get_browser_host(browser);
    let close_fn = unsafe { (*browser_host).close_browser.expect("null close_browser") };
    unsafe { close_fn(browser_host, force) };
}

#[no_mangle]
pub extern fn cefswt_load_url(browser: *mut cef::cef_browser_t, url: *const c_char, post_bytes: *const c_void, post_size: usize, headers: *const c_char, headers_size: usize) {
    let url = utils::str_from_c(url);
    let url_cef = utils::cef_string(url);
    //println!("url: {:?}", url);
    unsafe {
        let get_frame = (*browser).get_main_frame.expect("null get_main_frame");
        let main_frame = get_frame(browser);
        if post_bytes.is_null() && headers.is_null() {
            (*main_frame).load_url.unwrap()(main_frame, &url_cef);
        } else {
            let request = cef::cef_request_create();
            (*request).set_url.unwrap()(request, &url_cef);
            if !post_bytes.is_null() {
                let post_data = cef::cef_post_data_create();
                let post_element = cef::cef_post_data_element_create();
                (*post_element).set_to_bytes.unwrap()(post_element, post_size, post_bytes);
                (*post_data).add_element.unwrap()(post_data, post_element);

                (*request).set_post_data.unwrap()(request, post_data);
            }

            if !headers.is_null() {
                let map = cef::cef_string_multimap_alloc();

                let headers = utils::str_from_c(headers);
                let headers: Vec<&str> = headers.splitn(headers_size, "::").collect();
                for i in 0..headers_size {
                    // let header = headers.wrapping_add(i);
                    // let ptr = header.read();

                    // let header_str = utils::str_from_c(ptr);
                    let header_str = headers[i];
                    let header: Vec<&str> = header_str.splitn(2, ':').collect();
                    let key = header[0].trim();
                    let value = header[1].trim();
                    let key = utils::cef_string(key);
                    let value = utils::cef_string(value);

                    cef::cef_string_multimap_append(map, &key, &value);
                }
                (*request).set_header_map.unwrap()(request, map);
            }

            (*main_frame).load_request.unwrap()(main_frame, request);
        }
    }
}

#[no_mangle]
pub extern fn cefswt_get_url(browser: *mut cef::cef_browser_t) -> *mut c_char {
    let get_frame = unsafe { (*browser).get_main_frame.expect("null get_main_frame") };
    let main_frame = unsafe { get_frame(browser) };
    assert!(!main_frame.is_null());
    let get_url = unsafe { (*main_frame).get_url.expect("null get_url") };
    let url = unsafe { get_url(main_frame) };
    utils::cstr_from_cef(url)
}

#[no_mangle]
pub extern fn cefswt_cefstring_to_java(cefstring: *mut cef::cef_string_t) -> *const c_char {
    let r = utils::cstr_from_cef(cefstring);
    r
}

#[no_mangle]
pub extern fn cefswt_request_to_java(request: *mut cef::cef_request_t) -> *mut c_char {
    let url = unsafe { (*request).get_url.expect("null get_url")(request) };
    let cstr = utils::cstr_from_cef(url);
    unsafe { cef::cef_string_userfree_utf16_free(url) };
    cstr
}

#[no_mangle]
pub extern fn cefswt_cookie_to_java(cookie: *mut cef::_cef_cookie_t) -> *mut c_char {
    let name = unsafe { (*cookie).name };
    let cstr = utils::cstr_from_cef(&name);
    cstr
}

#[no_mangle]
pub extern fn cefswt_load_text(browser: *mut cef::cef_browser_t, text: *const c_char) {
    let text = utils::str_from_c(text);
    let text_cef = utils::cef_string(text);
    let url_cef = utils::cef_string("http://text/");
    // println!("text: {:?}", text);
    let get_frame = unsafe { (*browser).get_main_frame.expect("null get_main_frame") };
    let main_frame = unsafe { get_frame(browser) };
    let load_string = unsafe { (*main_frame).load_string.expect("null load_string") };
    unsafe { load_string(main_frame, &text_cef, &url_cef) };
}

#[no_mangle]
pub extern fn cefswt_stop(browser: *mut cef::cef_browser_t) {
    unsafe { (*browser).stop_load.expect("null stop_load")(browser); };
}

#[no_mangle]
pub extern fn cefswt_reload(browser: *mut cef::cef_browser_t) {
    unsafe { (*browser).reload.expect("null reload")(browser); };
}

#[no_mangle]
pub extern fn cefswt_get_text(browser: *mut cef::cef_browser_t, visitor: *mut cef::_cef_string_visitor_t) {
    assert_eq!(unsafe{(*visitor).base.size}, std::mem::size_of::<cef::_cef_string_visitor_t>());
    let get_frame = unsafe { (*browser).get_main_frame.expect("null get_main_frame") };
    let main_frame = unsafe { get_frame(browser) };
    assert!(!main_frame.is_null());
    let get_text = unsafe { (*main_frame).get_source.expect("null get_text") };
    // println!("before get_text");
    unsafe { get_text(main_frame, visitor) };
    // println!("after get_text");
}

#[no_mangle]
pub extern fn cefswt_execute(browser: *mut cef::cef_browser_t, text: *const c_char) {
    let text = utils::str_from_c(text);
    let text_cef = utils::cef_string(text);
    let url_cef = utils::cef_string_empty();
    let get_frame = unsafe { (*browser).get_main_frame.expect("null get_main_frame") };
    let main_frame = unsafe { get_frame(browser) };
    let execute = unsafe { (*main_frame).execute_java_script.expect("null execute_java_script") };
    unsafe { execute(main_frame, &text_cef, &url_cef, 0) };
}

#[no_mangle]
pub extern fn cefswt_eval(browser: *mut cef::cef_browser_t, text: *const c_char, id: i32,
        callback: unsafe extern "system" fn(work: c_int, kind: c_int, value: *const c_char)) -> c_int {
    let text_cef = utils::cef_string_from_c(text);
    let name = utils::cef_string("eval");
    unsafe {
        let msg = cef::cef_process_message_create(&name);
        let args = (*msg).get_argument_list.unwrap()(msg);
        let s = (*args).set_int.unwrap()(args, 1, id);
        assert_eq!(s, 1);
        let s = (*args).set_string.unwrap()(args, 2, &text_cef);
        assert_eq!(s, 1);
        match socket::wait_response(browser, msg, args, cef::cef_process_id_t::PID_RENDERER, Some(callback)) {
            Ok(r) => {
                callback(0, r.kind as i32, r.str_value.as_ptr());
                1
            },
            Err(_e) => {
                //println!("Failed in socket server {:?}", e);
                0
            }
        }
    }
}

#[no_mangle]
pub extern fn cefswt_function(browser: *mut cef::cef_browser_t, name: *mut c_char, id: i32) -> c_int {
    let name_cef = utils::cef_string_from_c(name);
    let msg_name = utils::cef_string("function");
    unsafe {
        let msg = cef::cef_process_message_create(&msg_name);
        let args = (*msg).get_argument_list.unwrap()(msg);
        let s = (*args).set_int.unwrap()(args, 0, id);
        assert_eq!(s, 1);
        let s = (*args).set_string.unwrap()(args, 1, &name_cef);
        assert_eq!(s, 1);
        let sent = (*browser).send_process_message.unwrap()(browser, cef::cef_process_id_t::PID_RENDERER, msg);
        assert_eq!(sent, 1);
        sent
    }
}

#[repr(C)]
#[derive(Debug)]
pub struct FunctionSt {
    pub id: i32,
    pub port: i32,
    pub args: usize,
}

#[no_mangle]
pub unsafe extern fn cefswt_function_id(message: *mut cef::cef_process_message_t, st: *mut FunctionSt) {
    let valid = (*message).is_valid.unwrap()(message);
    let name = (*message).get_name.unwrap()(message);
    // let mut st = FunctionSt {id: -1, args: 0, port: 0};
    (*st).id = -1;
    (*st).args = 0;
    (*st).port = 0;
    if valid == 1 && cef::cef_string_utf16_cmp(&utils::cef_string("function_call"), name) == 0 {
        let args = (*message).get_argument_list.unwrap()(message);
        let args_len = (*args).get_size.unwrap()(args);
        let port = (*args).get_int.unwrap()(args, 0);
        // st = FunctionSt {
        //     id: (*args).get_int.unwrap()(args, 1),
        //     args: (args_len-1) / 2,
        //     port
        // };
        (*st).id = (*args).get_int.unwrap()(args, 1);
        (*st).args = (args_len-1) / 2;
        (*st).port = port; 
    }
    // let r = Box::new(st);
    // let r = Box::into_raw(r);
    // return r;
}

#[no_mangle]
pub unsafe extern fn cefswt_function_arg(message: *mut cef::cef_process_message_t, index: i32, callback: unsafe extern "system" fn(work: c_int, kind: c_int, value: *const c_char)) -> c_int {
    let args = (*message).get_argument_list.unwrap()(message);
    let kind = (*args).get_int.unwrap()(args, (1+index*2+1) as usize);
    let arg = (*args).get_string.unwrap()(args, (1+index*2+2) as usize);
    let cstr = utils::cstr_from_cef(arg);
    let kind = socket::ReturnType::from(kind);
    callback(0, kind as i32, cstr);
    1
}

#[no_mangle]
pub extern fn cefswt_function_return(_browser: *mut cef::cef_browser_t, _id: i32, port: i32, kind: socket::ReturnType, ret: *const c_char) -> c_int {
    let cstr = unsafe { std::ffi::CStr::from_ptr(ret) };
    let s = socket::socket_client(port as u16, cstr.to_owned(), kind);
    s
}

#[no_mangle]
pub extern fn cefswt_set_focus(browser: *mut cef::cef_browser_t, set: bool, parent: *mut c_void) {
    let browser_host = get_browser_host(browser);
    let focus_fn = unsafe { (*browser_host).set_focus.expect("null set_focus") };
    let focus = if set {
        1
    } else {
        0
    };
    //println!("<<<<<<<< set_focus {}", focus);
    unsafe { focus_fn(browser_host, focus) };
    if !set && !parent.is_null() {
        do_set_focus(parent, focus);
    }
}

#[cfg(target_os = "linux")]
fn do_set_focus(parent: *mut c_void, _focus: i32) {
    let root = unsafe { gtk::gtk_widget_get_toplevel(parent) };
    //println!("<<<<<<<< set_focus {} {:?} {:?}", focus, parent, root);
    // workaround to actually remove focus from cef inputs
    unsafe { gtk::gtk_window_present(root) };
}

#[cfg(target_family = "windows")]
fn do_set_focus(_parent: *mut c_void, _focus: i32) {
    // TODO
}

#[cfg(target_os = "macos")]
fn do_set_focus(_parent: *mut c_void, _focus: i32) {
    // handled by cocoa
}

#[no_mangle]
pub extern fn cefswt_is_same(browser: *mut cef::cef_browser_t, that: *mut cef::cef_browser_t) -> c_int {
    unsafe { (*browser).is_same.unwrap()(browser, that) }
}

#[no_mangle]
pub extern fn cefswt_dialog_close(callback: *mut cef::_cef_jsdialog_callback_t, success: c_int, prompt: *mut cef::cef_string_t) {
    unsafe { (*callback).cont.unwrap()(callback, success, prompt) };
}

#[no_mangle]
pub extern fn cefswt_context_menu_cancel(callback: *mut cef::_cef_run_context_menu_callback_t) {
    unsafe { (*callback).cancel.unwrap()(callback) };
}

#[no_mangle]
pub extern fn cefswt_auth_callback(callback: *mut cef::_cef_auth_callback_t, juser: *const c_char, jpass: *const c_char, cont: c_int) {
    unsafe {
        if cont == 1 {
            let user = utils::cef_string_from_c(juser);
            let pass = utils::cef_string_from_c(jpass);
            (*callback).cont.unwrap()(callback, &user, &pass)
        } else {
            (*callback).cancel.unwrap()(callback) 
        }
    };
}

#[no_mangle]
pub extern fn cefswt_set_cookie(jurl: *const c_char, jname: *const c_char, jvalue: *const c_char, jdomain: *const c_char, jpath: *const c_char, secure: i32, httponly: i32, max_age: f64) -> c_int {
    let manager = unsafe { cef::cef_cookie_manager_get_global_manager(std::ptr::null_mut()) };
    let url = utils::cef_string_from_c(jurl);
    let domain = utils::cef_string_from_c(jdomain);
    let path = utils::cef_string_from_c(jpath);
    let name = utils::cef_string_from_c(jname);
    let value = utils::cef_string_from_c(jvalue);
    let has_expires = if max_age == -1.0 {
        0
    } else {
        1
    };
    let mut expires = cef::cef_time_t { year: 0, month: 0, day_of_week: 0, day_of_month: 0, hour: 0, minute: 0, second: 0, millisecond: 0 };

    if max_age == -1.0 {
        unsafe { cef::cef_time_from_doublet(max_age, &mut expires) };
    }

    let cookie = cef::_cef_cookie_t {
        name: name,
        value: value,
        domain: domain,
        path: path,
        secure,
        httponly,
        has_expires,
        expires,
        creation: expires,
        last_access: expires
    };
    unsafe { (*manager).set_cookie.expect("null set_cookie")(manager, &url, &cookie, std::ptr::null_mut()) }
}

#[no_mangle]
pub extern fn cefswt_get_cookie(jurl: *const c_char, jvisitor: *mut cef::_cef_cookie_visitor_t) -> c_int {
    let manager = unsafe { cef::cef_cookie_manager_get_global_manager(std::ptr::null_mut()) };
    let url = utils::cef_string_from_c(jurl);

    unsafe { (*manager).visit_url_cookies.expect("null visit_url_cookies")(manager, &url, 1, jvisitor) }
}

#[no_mangle]
pub extern fn cefswt_cookie_value(cookie: *mut cef::_cef_cookie_t) -> *mut c_char {
    unsafe { utils::cstr_from_cef(&mut (*cookie).value) }
}

#[no_mangle]
pub extern fn cefswt_delete_cookies() {
    let manager = unsafe { cef::cef_cookie_manager_get_global_manager(std::ptr::null_mut()) };
    unsafe { (*manager).delete_cookies.expect("null delete_cookies")(manager, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut()) };
}

#[no_mangle]
pub extern fn cefswt_shutdown() {
    //println!("r: Calling cef_shutdown");
    // Shut down CEF.
    unsafe { cef::cef_shutdown() };
    // println!("r: After Calling cef_shutdown");
}

fn get_browser_host(browser: *mut cef::cef_browser_t) -> *mut cef::_cef_browser_host_t {
    let get_host_fn = unsafe { (*browser).get_host.expect("null get_host") };
    let browser_host = unsafe { get_host_fn(browser) };
    browser_host
}

#[no_mangle]
pub extern fn cefswt_is_main_frame(frame: *mut cef::_cef_frame_t) -> i32 {
    unsafe { (*frame).is_main.expect("null is_main")(frame) }
}

#[no_mangle]
pub extern fn cefswt_go_forward(browser: *mut cef::_cef_browser_t) {
    unsafe { (*browser).go_forward.expect("null go_forward")(browser) };
}

#[no_mangle]
pub extern fn cefswt_go_back(browser: *mut cef::_cef_browser_t) {
    unsafe { (*browser).go_back.expect("null go_back")(browser) };
}