/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


use eutil::slice_to_str;
use libc::{self, size_t, c_int, c_ushort, c_void, wchar_t};
use std::char;
use std::cmp::Ordering;
use std::mem;
use std::ptr;
use std::slice;
use std::string;
use types::{cef_string_userfree_utf16_t, cef_string_userfree_utf8_t, cef_string_userfree_wide_t};
use types::{cef_string_utf16_t, cef_string_utf8_t, cef_string_wide_t};

//cef_string

#[no_mangle]
#[allow(private_no_mangle_fns)]
extern "C" fn string_wide_dtor(str: *mut wchar_t) {
    unsafe {
        libc::free(str as *mut c_void)
    }
}

#[no_mangle]
#[allow(private_no_mangle_fns)]
extern "C" fn string_utf8_dtor(str: *mut u8) {
    unsafe {
        libc::free(str as *mut c_void)
    }
}

#[no_mangle]
#[allow(private_no_mangle_fns)]
extern "C" fn string_utf16_dtor(str: *mut c_ushort) {
    unsafe {
        libc::free(str as *mut c_void)
    }
}

#[no_mangle]
pub extern "C" fn cef_string_userfree_wide_free(cs: *mut cef_string_userfree_wide_t) {
    cef_string_wide_clear(cs);
    unsafe {
        libc::free(cs as *mut c_void)
    }
}

#[no_mangle]
pub extern "C" fn cef_string_userfree_utf8_free(cs: *mut cef_string_userfree_utf8_t) {
    unsafe {
        cef_string_utf8_clear(cs);
        libc::free(cs as *mut c_void)
    }
}

#[no_mangle]
pub extern "C" fn cef_string_userfree_utf16_free(cs: cef_string_userfree_utf16_t) {
    unsafe {
        cef_string_utf16_clear(cs);
        libc::free(cs as *mut c_void)
    }
}

#[no_mangle]
pub extern "C" fn cef_string_utf8_clear(cs: *mut cef_string_utf8_t) {
    unsafe {
        (*cs).dtor.map(|dtor| dtor((*cs).str));
        (*cs).length = 0;
        (*cs).str = 0 as *mut u8;
        (*cs).dtor = None;
    }
}

#[inline(never)]
#[no_mangle]
pub extern "C" fn cef_string_userfree_utf8_alloc() -> *mut cef_string_utf8_t {
    unsafe {
        libc::calloc(1, mem::size_of::<cef_string_utf8_t>()) as *mut cef_string_utf8_t
    }
}

#[no_mangle]
pub extern "C" fn cef_string_utf8_set(src: *const u8, src_len: size_t, output: *mut cef_string_utf8_t, copy: c_int) -> c_int {
    cef_string_utf8_clear(output);
    unsafe {
       if copy != 0 {
           if !src.is_null() && src_len > 0 {
               (*output).str = libc::calloc(1, src_len + 1) as *mut u8;
               if (*output).str.is_null() {
                   return 0;
               }

               ptr::copy(src, (*output).str, src_len as usize);
               (*output).length = src_len;
               (*output).dtor = Some(string_utf8_dtor as extern "C" fn(*mut u8));
           }
       } else {
           (*output).str = src as *mut _;
           (*output).length = src_len;
           (*output).dtor = None;
       }
    }
    return 1;
}

#[no_mangle]
pub extern "C" fn cef_string_utf8_cmp(a: *const cef_string_utf8_t, b: *const cef_string_utf8_t) -> c_int {
    unsafe {
        let astr = (*a).str as *const u8;
        let bstr = (*b).str as *const u8;
        let astr = slice::from_raw_parts(astr, (*a).length as usize);
        let bstr = slice::from_raw_parts(bstr, (*b).length as usize);
        match astr.cmp(bstr) {
            Ordering::Less => -1,
            Ordering::Equal => 0,
            Ordering::Greater => 1
        }
    }
}

#[no_mangle]
pub extern "C" fn cef_string_utf8_to_utf16(src: *const u8, src_len: size_t, output: *mut cef_string_utf16_t) -> c_int {
    slice_to_str(src, src_len as usize, |result| {
        let conv = result.encode_utf16().collect::<Vec<u16>>();
        cef_string_utf16_set(conv.as_ptr(), conv.len() as size_t, output, 1);
        1
    })
}

#[no_mangle]
pub extern "C" fn cef_string_utf16_to_utf8(src: *const u16, src_len: size_t, output: *mut cef_string_utf8_t) -> c_int {
    unsafe {
        let ustr = slice::from_raw_parts(src, src_len as usize);
        match string::String::from_utf16(ustr) {
            Ok(str) => {
                cef_string_utf8_set(str.as_bytes().as_ptr(), str.len() as size_t, output, 1);
                1 as c_int
            },
            _ =>  0 as c_int
        }
    }
}

#[no_mangle]
pub extern "C" fn cef_string_utf16_clear(cs: *mut cef_string_utf16_t) {
    unsafe {
        (*cs).dtor.map(|dtor| dtor((*cs).str));
        (*cs).length = 0;
        (*cs).str = 0 as *mut c_ushort;
        (*cs).dtor = None;
    }
}

#[inline(never)]
#[no_mangle]
pub extern "C" fn cef_string_userfree_utf16_alloc() -> *mut cef_string_utf16_t {
    unsafe {
        libc::calloc(1, mem::size_of::<cef_string_utf16_t>()) as *mut cef_string_utf16_t
    }
}

#[no_mangle]
pub extern "C" fn cef_string_utf16_set(src: *const c_ushort, src_len: size_t, output: *mut cef_string_utf16_t, copy: c_int) -> c_int {
    cef_string_utf16_clear(output);
    unsafe {
       if copy != 0 {
           if !src.is_null() && src_len > 0 {
               (*output).str = libc::calloc(1, (src_len + 1) * mem::size_of::<c_ushort>()) as
                   *mut u16;
               if (*output).str.is_null() {
                   return 0;
               }

               ptr::copy(src, (*output).str, src_len as usize);
               (*output).length = src_len;
               (*output).dtor = Some(string_utf16_dtor as extern "C" fn(*mut c_ushort));
           }
       } else {
           (*output).str = src as *mut _;
           (*output).length = src_len;
           (*output).dtor = None;
       }
    }
    return 1;
}

#[no_mangle]
pub extern "C" fn cef_string_utf16_cmp(a: *const cef_string_utf16_t, b: *const cef_string_utf16_t) -> c_int {
    unsafe {
        let astr = (*a).str as *const _;
        let bstr = (*b).str as *const _;
        let astr: &[u16] = slice::from_raw_parts(astr, (*a).length as usize);
        let bstr: &[u16] = slice::from_raw_parts(bstr, (*b).length as usize);
        match astr.cmp(bstr) {
            Ordering::Less => -1,
            Ordering::Equal => 0,
            Ordering::Greater => 1
        }
    }
}

#[no_mangle]
pub extern "C" fn cef_string_wide_clear(cs: *mut cef_string_wide_t) {
    unsafe {
        (*cs).dtor.map(|dtor| dtor((*cs).str));
        (*cs).length = 0;
        (*cs).str = 0 as *mut wchar_t;
        (*cs).dtor = None;
    }
}

#[inline(never)]
#[no_mangle]
pub extern "C" fn cef_string_userfree_wide_alloc() -> *mut cef_string_wide_t {
    unsafe {
        libc::calloc(1, mem::size_of::<cef_string_wide_t>()) as *mut cef_string_wide_t
    }
}

#[no_mangle]
pub extern "C" fn cef_string_wide_set(src: *const wchar_t, src_len: size_t, output: *mut cef_string_wide_t, copy: c_int) -> c_int {
    cef_string_wide_clear(output);
    unsafe {
       if copy != 0 {
           if !src.is_null() && src_len > 0 {
               (*output).str = libc::calloc(1, (src_len + 1) * mem::size_of::<wchar_t>()) as
                   *mut wchar_t;
               if (*output).str.is_null() {
                   return 0;
               }

               ptr::copy(src, (*output).str, src_len as usize);
               (*output).length = src_len;
               (*output).dtor = Some(string_wide_dtor as extern "C" fn(*mut wchar_t));
           }
       } else {
           (*output).str = src as *mut _;
           (*output).length = src_len;
           (*output).dtor = None;
       }
    }
    return 1;
}

#[no_mangle]
pub extern "C" fn cef_string_wide_cmp(a: *const cef_string_wide_t, b: *const cef_string_wide_t) -> c_int {
    unsafe {
        let astr = (*a).str as *const wchar_t;
        let bstr = (*b).str as *const wchar_t;
        let astr = slice::from_raw_parts(astr, (*a).length as usize);
        let bstr = slice::from_raw_parts(bstr, (*b).length as usize);
        match astr.cmp(bstr) {
            Ordering::Less => -1,
            Ordering::Equal => 0,
            Ordering::Greater => 1
        }
    }
}

#[no_mangle]
pub extern "C" fn cef_string_utf8_to_wide(src: *const u8, src_len: size_t, output: *mut cef_string_wide_t) -> c_int {
    if mem::size_of::<wchar_t>() == mem::size_of::<u16>() {
         return cef_string_utf8_to_utf16(src, src_len, output as *mut cef_string_utf16_t);
    }
    slice_to_str(src, src_len as usize, |result| {
        let conv = result.chars().map(|c| c as u32).collect::<Vec<u32>>();
        cef_string_wide_set(conv.as_ptr() as *const wchar_t, conv.len() as size_t, output, 1)
    })
}

#[no_mangle]
pub extern "C" fn cef_string_wide_to_utf8(src: *const wchar_t, src_len: size_t, output: *mut cef_string_utf8_t) -> c_int {
    if mem::size_of::<wchar_t>() == mem::size_of::<u16>() {
         return cef_string_utf16_to_utf8(src as *const u16, src_len, output);
    }
    unsafe {
        let ustr = slice::from_raw_parts(src, src_len as usize);
        let conv = ustr.iter().map(|&c| char::from_u32(c as u32).unwrap_or('\u{FFFD}')).collect::<String>();
        cef_string_utf8_set(conv.as_bytes().as_ptr(), conv.len() as size_t, output, 1)
    }
}

#[no_mangle]
pub extern "C" fn cef_string_ascii_to_utf16(src: *const u8, src_len: size_t, output: *mut cef_string_utf16_t) -> c_int {
    slice_to_str(src, src_len as usize, |result| {
        let conv = result.encode_utf16().collect::<Vec<u16>>();
        cef_string_utf16_set(conv.as_ptr(), conv.len() as size_t, output, 1)
    })
}

#[no_mangle]
pub extern "C" fn cef_string_ascii_to_wide(src: *const u8, src_len: size_t, output: *mut cef_string_wide_t) -> c_int {
    unsafe {
        let ustr = slice::from_raw_parts(src, src_len as usize);
        let conv = ustr.iter().map(|&c| c as u8).collect::<Vec<u8>>();
        cef_string_wide_set(conv.as_ptr() as *const wchar_t, conv.len() as size_t, output, 1)
    }
}

pub fn empty_utf16_string() -> cef_string_utf16_t {
    cef_string_utf16_t {
        str: ptr::null_mut(),
        length: 0,
        dtor: None,
    }
}

pub fn string_to_userfree_string(string: cef_string_utf16_t) -> cef_string_userfree_utf16_t {
    unsafe {
        let allocation = libc::malloc(mem::size_of::<cef_string_utf16_t>() as size_t)
            as cef_string_userfree_utf16_t;
        ptr::write(allocation, string);
        allocation
    }
}

pub fn empty_utf16_userfree_string() -> cef_string_userfree_utf16_t {
    string_to_userfree_string(empty_utf16_string())
}

