use {Handler, Options, Window};
use std::{process, slice, str};
use std::os::raw::{c_int, c_void};

pub fn start<H: Handler>(opts: Options<H>) -> ! {
    unsafe {
        let min_size = opts.minimum_size.unwrap_or((opts.width, opts.height));

        tether_start(
            TetherString::new(opts.html),
            opts.width,
            opts.height,
            min_size.0,
            min_size.1,
            if opts.fullscreen { 1 } else { 0 },

            Box::into_raw(Box::new(opts.handler)) as *mut c_void,
            message::<H>,
            suspend::<H>,
        )
    };

    process::exit(0)
}

unsafe extern "C" fn message<H: Handler>(handler: *mut c_void, msg: TetherString) {
    let handler = handler as *mut H;
    (*handler).message(Window::new(), msg.to_str());
}

unsafe extern "C" fn suspend<H: Handler>(handler: *mut c_void) {
    let handler = handler as *mut H;
    (*handler).suspend(Window::new());
}

pub fn load(html: &str) {
    unsafe {
        tether_load(TetherString::new(html))
    };
}

pub fn eval(js: &str) {
    unsafe {
        tether_eval(TetherString::new(js))
    };
}

pub fn dispatch<F: FnOnce(Window) + Send + 'static>(f: F) {
    unsafe {
        tether_dispatch(
            Box::into_raw(Box::new(f)) as *mut c_void,
            exec::<F>,
        )
    };

    unsafe extern "C" fn exec<F: FnOnce(Window) + Send + 'static>(f: *mut c_void) {
        Box::from_raw(f as *mut F)(Window::new());
    }
}

// FOREIGN STUFF

#[repr(C)]
struct TetherString {
    len: usize,
    ptr: *const u8,
}

impl TetherString {
    fn new(x: &str) -> TetherString {
        TetherString { len: x.len(), ptr: x.as_ptr() }
    }

    unsafe fn to_str<'a>(self) -> &'a str {
        str::from_utf8_unchecked(slice::from_raw_parts(self.ptr, self.len))
    }
}

extern "C" {
    fn tether_start(
        html: TetherString,
        width: usize,
        height: usize,
        min_width: usize,
        min_height: usize,
        fullscreen: c_int,

        han_data: *mut c_void,
        han_message: unsafe extern "C" fn(*mut c_void, TetherString),
        han_suspend: unsafe extern "C" fn(*mut c_void)
    );

    fn tether_load(html: TetherString);

    fn tether_eval(js: TetherString);

    fn tether_dispatch(
        data: *mut c_void,
        exec: unsafe extern "C" fn(*mut c_void),
    );
}
