//! C FFI for cpdf image operations.

use super::document::{CPDF_DOCS, CPDF_RANGES};
use super::{clear_error, set_error};
use crate::cpdf::images;
use crate::ffi::Handle;
use std::ffi::{CStr, c_char, c_double, c_int};

macro_rules! get_doc_ref {
    ($handle:expr) => {
        match CPDF_DOCS.get($handle) {
            Some(arc) => arc,
            None => {
                set_error(1, &format!("invalid document handle: {}", $handle));
                return std::ptr::null_mut();
            }
        }
    };
}

macro_rules! get_range {
    ($handle:expr) => {
        match CPDF_RANGES.get($handle) {
            Some(arc) => arc.lock().unwrap().clone(),
            None => {
                set_error(1, &format!("invalid range handle: {}", $handle));
                return std::ptr::null_mut();
            }
        }
    };
    ($handle:expr, $ret:expr) => {
        match CPDF_RANGES.get($handle) {
            Some(arc) => arc.lock().unwrap().clone(),
            None => {
                set_error(1, &format!("invalid range handle: {}", $handle));
                return $ret;
            }
        }
    };
}

/// Get image list as JSON. Caller frees with `cpdf_free(ptr, *len_out)`.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_getImageListJSON(
    doc: Handle,
    range_h: Handle,
    len_out: *mut usize,
) -> *mut u8 {
    clear_error();
    let d = get_doc_ref!(doc);
    let guard = d.lock().unwrap();
    let r = get_range!(range_h);
    match images::list_images_json(&guard, &r) {
        Ok(json) => {
            let bytes = json.into_bytes();
            let len = bytes.len();
            let ptr = unsafe {
                let layout = std::alloc::Layout::from_size_align_unchecked(len, 1);
                let p = std::alloc::alloc(layout);
                if p.is_null() {
                    set_error(1, "allocation failed");
                    return std::ptr::null_mut();
                }
                std::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
                p
            };
            if !len_out.is_null() {
                unsafe { *len_out = len };
            }
            ptr
        }
        Err(e) => {
            set_error(1, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Get image resolution JSON. Caller frees with `cpdf_free`.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_imageResolutionJSON(
    doc: Handle,
    range_h: Handle,
    min_dpi: c_double,
    len_out: *mut usize,
) -> *mut u8 {
    clear_error();
    let d = get_doc_ref!(doc);
    let guard = d.lock().unwrap();
    let r = get_range!(range_h);
    match images::image_resolution(&guard, &r, min_dpi) {
        Ok(info) => {
            let json = match serde_json::to_string_pretty(&info) {
                Ok(j) => j,
                Err(e) => {
                    set_error(1, &e.to_string());
                    return std::ptr::null_mut();
                }
            };
            let bytes = json.into_bytes();
            let len = bytes.len();
            let ptr = unsafe {
                let layout = std::alloc::Layout::from_size_align_unchecked(len, 1);
                let p = std::alloc::alloc(layout);
                if p.is_null() {
                    set_error(1, "allocation failed");
                    return std::ptr::null_mut();
                }
                std::ptr::copy_nonoverlapping(bytes.as_ptr(), p, len);
                p
            };
            if !len_out.is_null() {
                unsafe { *len_out = len };
            }
            ptr
        }
        Err(e) => {
            set_error(1, &e.to_string());
            std::ptr::null_mut()
        }
    }
}

/// Extract images to directory. Returns 0 on success, 1 on error.
#[unsafe(no_mangle)]
pub extern "C" fn cpdf_extractImages(
    doc: Handle,
    range_h: Handle,
    output_dir: *const c_char,
) -> c_int {
    clear_error();
    let d = match CPDF_DOCS.get(doc) {
        Some(arc) => arc,
        None => {
            set_error(1, &format!("invalid document handle: {}", doc));
            return 1;
        }
    };
    let guard = d.lock().unwrap();
    let r = get_range!(range_h, 1);
    let dir = if output_dir.is_null() {
        "."
    } else {
        unsafe { CStr::from_ptr(output_dir) }
            .to_str()
            .unwrap_or(".")
    };
    match images::extract_images(&guard, &r, dir) {
        Ok(()) => 0,
        Err(e) => {
            set_error(1, &e.to_string());
            1
        }
    }
}
