use std::ffi::CString;
use libc;
use crate::{LuaPush, LuaRead, lua_State, sys} ;

pub struct RawString(pub Vec<u8>);

macro_rules! integer_impl(
    ($t:ident) => (
        impl LuaPush for $t {
            fn push_to_lua(self, lua: *mut lua_State) -> i32 {
                unsafe { sys::lua_pushinteger(lua, self as sys::lua_Integer) };
                1
            }
        }

        impl LuaPush for &$t {
            fn push_to_lua(self, lua: *mut lua_State) -> i32 {
                unsafe { sys::lua_pushinteger(lua, *self as sys::lua_Integer) };
                1
            }
        }

        impl LuaRead for $t {
            fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<$t> {
                let mut success = 0;
                let val = unsafe { sys::lua_tointegerx(lua, index, &mut success) };
                match success {
                    0 => None,
                    _ => Some(val as $t)
                }
            }
        }
    );
);

integer_impl!(i8);
integer_impl!(i16);
integer_impl!(i32);
integer_impl!(i64);
integer_impl!(u8);
integer_impl!(u16);
integer_impl!(u32);
integer_impl!(u64);
integer_impl!(usize);


macro_rules! numeric_impl(
    ($t:ident) => (
        impl LuaPush for $t {
            fn push_to_lua(self, lua: *mut lua_State) -> i32 {
                unsafe { sys::lua_pushnumber(lua, self as f64) };
                1
            }
        }

        impl LuaPush for &$t {
            fn push_to_lua(self, lua: *mut lua_State) -> i32 {
                unsafe { sys::lua_pushnumber(lua, *self as f64) };
                1
            }
        }

        impl LuaRead for $t {
            fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<$t> {
                let mut success = 0;
                let val = unsafe { sys::lua_tonumberx(lua, index, &mut success) };
                match success {
                    0 => None,
                    _ => Some(val as $t)
                }
            }
        }
    );
);

numeric_impl!(f32);
numeric_impl!(f64);


impl LuaPush for &String {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        if let Some(value) = CString::new(&self[..]).ok() {
            unsafe { sys::lua_pushstring(lua, value.as_ptr()) };
            1
        } else {
            unsafe { sys::lua_pushstring(lua, cstr!("UNVAILED STRING")) };
            1
        }
    }
}

impl LuaPush for String {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        (&self).push_to_lua(lua)
    }
}

impl LuaRead for String {
    fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<String> {
        if unsafe {
            sys::lua_isstring(lua, index) == 0
        } {
            return None;
        }
        let mut size = 0;
        let data = unsafe { sys::lua_tolstring(lua, index, &mut size) };
        let bytes = unsafe { std::slice::from_raw_parts(data as *const u8, size) };
        match std::str::from_utf8(bytes) {
            Ok(v) => Some(v.to_string()),
            Err(_) => Some("raw binary string...".to_string()),
        }
    }
}


impl LuaPush for &CString {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushstring(lua, self.as_ptr()) };
        1
    }
}

impl LuaPush for CString {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        (&self).push_to_lua(lua)
    }
}

impl LuaRead for CString {
    fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<CString> {
        let mut size = 0;
        let data = unsafe { sys::lua_tolstring(lua, index, &mut size) };
        let bytes = unsafe { std::slice::from_raw_parts(data as *const u8, size) };
        match std::str::from_utf8(bytes) {
            Ok(v) => CString::new(v).ok(),
            Err(_) => None,
        }
    }
}

impl<'s> LuaPush for &'s str {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        if let Some(value) = CString::new(&self[..]).ok() {
            unsafe { sys::lua_pushstring(lua, value.as_ptr()) };
            1
        } else {
            unsafe { sys::lua_pushstring(lua, cstr!("UNVAILED STRING")) };
            1
        }
    }
}

impl LuaPush for bool {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushboolean(lua, self.clone() as libc::c_int) };
        1
    }
}

impl LuaPush for &bool {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushboolean(lua, self.clone() as libc::c_int) };
        1
    }
}

impl LuaRead for bool {
    fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<bool> {
        if unsafe { !sys::lua_isboolean(lua, index) } {
            return None;
        }

        Some(unsafe { sys::lua_toboolean(lua, index) != 0 })
    }
}

impl LuaPush for () {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushnil(lua) };
        1
    }
}

impl LuaPush for &() {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushnil(lua) };
        1
    }
}

impl LuaRead for () {
    fn lua_read_with_pop_impl(_: *mut lua_State, _: i32, _pop: i32) -> Option<()> {
        Some(())
    }
}

impl LuaPush for &RawString {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushlstring(lua, self.0.as_ptr() as *const i8, self.0.len()) };
        1
    }
}

impl LuaPush for RawString {
    fn push_to_lua(self, lua: *mut lua_State) -> i32 {
        unsafe { sys::lua_pushlstring(lua, self.0.as_ptr() as *const i8, self.0.len()) };
        1
    }
}

impl LuaRead for RawString {
    fn lua_read_with_pop_impl(lua: *mut lua_State, index: i32, _pop: i32) -> Option<RawString> {
        let mut size: libc::size_t = 0;
        let c_str_raw = unsafe { sys::lua_tolstring(lua, index, &mut size) };
        if c_str_raw.is_null() {
            return None;
        }

        let value = unsafe { Vec::from_raw_parts(c_str_raw as *mut u8, size, size) };
        Some(RawString(value))
    }
}