//! Handling the foreign-function interface.

use crate::{panic_handling, NePacketTunnelFlow, OnionmasqApple};
use onion_tunnel::accounting::BandwidthCounter;
use onion_tunnel::scaffolding::TunnelCommand;
use std::ffi::{c_char, CStr};
use tracing::{debug, error, info};

pub type ReaderCallback = extern "C" fn();

pub type WriterCallback = extern "C" fn(packet: *const u8, len: usize) -> bool;

pub type EventCallback = extern "C" fn(*const c_char);

pub type LoggingCallback = extern "C" fn(*const c_char);

#[no_mangle]
pub extern "C" fn runProxy(
    reader: ReaderCallback,
    writer: WriterCallback,
    cache_dir: *const c_char,
    data_dir: *const c_char,
) {
    panic_handling::capture_unwind!({
        let oma = OnionmasqApple::get();
        let cache_dir = unsafe {
            CStr::from_ptr(cache_dir)
                .to_str()
                .expect("invalid UTF-8 in cache_dir")
        };
        let data_dir = unsafe {
            CStr::from_ptr(data_dir)
                .to_str()
                .expect("invalid UTF-8 in data_dir")
        };
        if let Err(e) = oma.run_proxy(cache_dir, data_dir, reader, writer) {
            error!("runProxy() returned error: {e:?}");
        }
    })
}

#[no_mangle]
pub extern "C" fn closeProxy() {
    panic_handling::capture_unwind!({
        let oma = OnionmasqApple::get();
        if let Err(e) = oma.stop_proxy() {
            error!("closeProxy() returned error: {e:?}");
        }
    })
}

#[no_mangle]
pub extern "C" fn refreshCircuits() {
    panic_handling::capture_unwind!({
        let oma = OnionmasqApple::get();
        if let Err(e) = oma.send_command(TunnelCommand::RefreshCircuits) {
            error!("send_command() returned error: {e:?}");
        }
    })
}

#[no_mangle]
pub extern "C" fn refreshCircuitsForApp(aid: u64) {
    panic_handling::capture_unwind!({
        let oma = OnionmasqApple::get();
        if let Err(e) =
            oma.send_command(TunnelCommand::RefreshCircuitsForApp { isolation_key: aid })
        {
            error!("send_command() returned error: {e:?}");
        }
    })
}

#[no_mangle]
pub extern "C" fn init(event_fn: EventCallback, log_fn: LoggingCallback) {
    panic_handling::capture_unwind!({
        if let Err(e) = OnionmasqApple::new(event_fn, log_fn) {
            error!("init() returned error: {e:?}");
        }
    })
}

#[no_mangle]
pub extern "C" fn setPcapPath(path: *const c_char) {
    panic_handling::capture_unwind!({
        debug!("setPcapPath()");
        let path = unsafe { CStr::from_ptr(path) }
            .to_str()
            .expect("path is invalid");
        info!("enabling pcap logging to path: {}", path);
        let path = if path.is_empty() {
            None
        } else {
            Some(path.to_string())
        };
        OnionmasqApple::get().set_pcap_path(path);
    })
}

#[no_mangle]
pub extern "C" fn getBytesReceived() -> i64 {
    panic_handling::capture_unwind!({
        let rx = BandwidthCounter::global().bytes_rx();
        // Realistically nobody is going to pass 8192 PiB of data through the tunnel, are they?
        rx.try_into().expect("wow, large bandwidth counter")
    })
}

#[no_mangle]
pub extern "C" fn getBytesReceivedForApp(aid: u64) -> i64 {
    panic_handling::capture_unwind!({
        let rx = OnionmasqApple::get().app_rx_counter(aid);
        rx.try_into().expect("wow, large bandwidth counter")
    })
}

#[no_mangle]
pub extern "C" fn getBytesSent() -> i64 {
    panic_handling::capture_unwind!({
        let tx = BandwidthCounter::global().bytes_tx();
        tx.try_into().expect("wow, large bandwidth counter")
    })
}

#[no_mangle]
pub extern "C" fn getBytesSentForApp(aid: u64) -> i64 {
    panic_handling::capture_unwind!({
        let tx = OnionmasqApple::get().app_tx_counter(aid);
        tx.try_into().expect("wow, large bandwidth counter")
    })
}

#[no_mangle]
pub extern "C" fn resetCounters() {
    panic_handling::capture_unwind!({ OnionmasqApple::get().reset_counters() })
}

#[no_mangle]
pub extern "C" fn setCountryCode(country_code: *const c_char) {
    panic_handling::capture_unwind!({
        if let Err(e) = OnionmasqApple::get().set_country_code(country_code) {
            error!("set_country_code() returned error: {e:?}");
        }
    })
}

#[no_mangle]
pub unsafe extern "C" fn receive(packets: *const *const u8, lens: *const usize, len: usize) {
    panic_handling::capture_unwind!({
        NePacketTunnelFlow::receive(packets, lens, len);
    })
}
