use std::{ffi::CStr, os::raw::c_char};

use crate::{
    error::KetError,
    instruction::{ClassicalOp, QuantumGate},
    object::{Dump, Future, Label, Qubit},
    process::Process,
    serialize::{DataType, SerializedData},
    Features,
};

use num::{FromPrimitive, ToPrimitive};

use super::error::wrapper;

#[no_mangle]
pub extern "C" fn ket_process_new(pid: usize, process: &mut *mut Process) -> i32 {
    *process = Box::into_raw(Box::new(Process::new(pid)));
    KetError::Success.error_code()
}

#[no_mangle]
pub unsafe extern "C" fn ket_process_delete(process: *mut Process) -> i32 {
    unsafe { Box::from_raw(process) };
    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_process_set_features(process: &mut Process, features: &Features) -> i32 {
    process.set_features(features.clone());
    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_process_allocate_qubit(
    process: &mut Process,
    dirty: bool,
    qubit: &mut *mut Qubit,
) -> i32 {
    match process.allocate_qubit(dirty) {
        Ok(result) => {
            *qubit = Box::into_raw(Box::new(result));
            KetError::Success.error_code()
        }
        Err(error) => error.error_code(),
    }
}

#[no_mangle]
pub extern "C" fn ket_process_free_qubit(
    process: &mut Process,
    qubit: &mut Qubit,
    dirty: bool,
) -> i32 {
    wrapper(process.free_qubit(qubit, dirty))
}

#[no_mangle]
pub extern "C" fn ket_process_apply_gate(
    process: &mut Process,
    gate: i32,
    param: f64,
    target: &Qubit,
) -> i32 {
    let gate = match gate {
        0 => QuantumGate::PauliX,
        1 => QuantumGate::PauliY,
        2 => QuantumGate::PauliZ,
        3 => QuantumGate::Hadamard,
        4 => QuantumGate::Phase(param),
        5 => QuantumGate::RX(param),
        6 => QuantumGate::RY(param),
        7 => QuantumGate::RZ(param),
        _ => {
            return KetError::UndefinedGate.error_code();
        }
    };

    wrapper(process.apply_gate(gate, target))
}

#[no_mangle]
pub unsafe extern "C" fn ket_process_apply_plugin(
    process: &mut Process,
    name: *const c_char,
    args: *const c_char,
    target: *const &Qubit,
    target_size: usize,
) -> i32 {
    let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
    let args = unsafe { CStr::from_ptr(args) }.to_str().unwrap();
    let target = unsafe { std::slice::from_raw_parts(target, target_size) };

    wrapper(process.apply_plugin(name, target, args))
}

#[no_mangle]
pub unsafe extern "C" fn ket_process_measure(
    process: &mut Process,
    qubits: *mut &mut Qubit,
    qubits_size: usize,
    future: &mut *mut Future,
) -> i32 {
    let qubits = unsafe { std::slice::from_raw_parts_mut(qubits, qubits_size) };

    match process.measure(qubits) {
        Ok(result) => {
            *future = Box::into_raw(Box::new(result));
            KetError::Success.error_code()
        }
        Err(error) => error.error_code(),
    }
}

#[no_mangle]
pub unsafe extern "C" fn ket_process_ctrl_push(
    process: &mut Process,
    qubits: *const &Qubit,
    qubits_size: usize,
) -> i32 {
    let qubits = unsafe { std::slice::from_raw_parts(qubits, qubits_size) };

    wrapper(process.ctrl_push(qubits))
}

#[no_mangle]
pub extern "C" fn ket_process_ctrl_pop(process: &mut Process) -> i32 {
    wrapper(process.ctrl_pop())
}

#[no_mangle]
pub extern "C" fn ket_process_adj_begin(process: &mut Process) -> i32 {
    wrapper(process.adj_begin())
}

#[no_mangle]
pub extern "C" fn ket_process_adj_end(process: &mut Process) -> i32 {
    wrapper(process.adj_end())
}

#[no_mangle]
pub extern "C" fn ket_process_get_label(process: &mut Process, label: &mut *mut Label) -> i32 {
    *label = Box::into_raw(Box::new(match process.get_label() {
        Ok(label) => label,
        Err(err) => return err.error_code(),
    }));
    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_process_open_block(process: &mut Process, label: &Label) -> i32 {
    wrapper(process.open_block(label))
}

#[no_mangle]
pub extern "C" fn ket_process_jump(process: &mut Process, label: &Label) -> i32 {
    wrapper(process.jump(label))
}

#[no_mangle]
pub extern "C" fn ket_process_branch(
    process: &mut Process,
    test: &Future,
    then: &Label,
    otherwise: &Label,
) -> i32 {
    wrapper(process.branch(test, then, otherwise))
}

#[no_mangle]
pub unsafe extern "C" fn ket_process_dump(
    process: &mut Process,
    qubits: *const &Qubit,
    qubits_size: usize,
    dump: &mut *mut Dump,
) -> i32 {
    let qubits = unsafe { std::slice::from_raw_parts(qubits, qubits_size) };

    match process.dump(qubits) {
        Ok(result) => {
            *dump = Box::into_raw(Box::new(result));
            KetError::Success.error_code()
        }
        Err(error) => error.error_code(),
    }
}

#[no_mangle]
pub extern "C" fn ket_process_add_int_op(
    process: &mut Process,
    op: i32,
    lhs: &Future,
    rhs: &Future,
    result: &mut *mut Future,
) -> i32 {
    let op = match ClassicalOp::from_i32(op) {
        Some(op) => op,
        None => return KetError::UndefinedClassicalOp.error_code(),
    };

    match process.add_int_op(op, lhs, rhs) {
        Ok(future) => {
            *result = Box::into_raw(Box::new(future));
            KetError::Success.error_code()
        }
        Err(error) => error.error_code(),
    }
}

#[no_mangle]
pub extern "C" fn ket_process_int_new(
    process: &mut Process,
    value: i64,
    future: &mut *mut Future,
) -> i32 {
    match process.int_new(value) {
        Ok(result) => {
            *future = Box::into_raw(Box::new(result));
            KetError::Success.error_code()
        }
        Err(error) => error.error_code(),
    }
}

#[no_mangle]
pub extern "C" fn ket_process_int_set(process: &mut Process, dst: &Future, src: &Future) -> i32 {
    wrapper(process.int_set(dst, src))
}

#[no_mangle]
pub extern "C" fn ket_process_prepare_for_execution(process: &mut Process) -> i32 {
    wrapper(process.prepare_for_execution())
}

#[no_mangle]
pub extern "C" fn ket_process_exec_time(process: &Process, time: &mut f64) -> i32 {
    match process.exec_time() {
        Some(t) => {
            *time = t;
            KetError::Success.error_code()
        }
        None => KetError::DataNotAvailable.error_code(),
    }
}

#[no_mangle]
pub extern "C" fn ket_process_set_timeout(process: &mut Process, timeout: u64) -> i32 {
    process.set_timeout(timeout);
    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_process_serialize_metrics(process: &mut Process, data_type: i32) -> i32 {
    let data_type = match DataType::from_i32(data_type) {
        Some(data_type) => data_type,
        None => return KetError::UndefinedDataType.error_code(),
    };

    process.serialize_metrics(data_type);

    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_process_serialize_quantum_code(process: &mut Process, data_type: i32) -> i32 {
    let data_type = match DataType::from_i32(data_type) {
        Some(data_type) => data_type,
        None => return KetError::UndefinedDataType.error_code(),
    };

    process.serialize_quantum_code(data_type);

    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_process_get_serialized_metrics(
    process: &Process,
    data: &mut *const u8,
    size: &mut usize,
    data_type: &mut i32,
) -> i32 {
    match process.get_serialized_metrics() {
        Some(metrics) => {
            match metrics {
                SerializedData::JSON(json) => {
                    *data = json.as_ptr();
                    *size = json.len();
                    *data_type = DataType::JSON.to_i32().unwrap();
                }
                SerializedData::BIN(bin) => {
                    *data = bin.as_ptr();
                    *size = bin.len();
                    *data_type = DataType::BIN.to_i32().unwrap();
                }
            }
            KetError::Success.error_code()
        }
        None => KetError::DataNotAvailable.error_code(),
    }
}

#[no_mangle]
pub extern "C" fn ket_process_get_serialized_quantum_code(
    process: &Process,
    data: &mut *const u8,
    size: &mut usize,
    data_type: &mut i32,
) -> i32 {
    match process.get_serialized_quantum_code() {
        Some(code) => {
            match code {
                SerializedData::JSON(json) => {
                    *data = json.as_ptr();
                    *size = json.len();
                    *data_type = DataType::JSON.to_i32().unwrap();
                }
                SerializedData::BIN(bin) => {
                    *data = bin.as_ptr();
                    *size = bin.len();
                    *data_type = DataType::BIN.to_i32().unwrap();
                }
            }
            KetError::Success.error_code()
        }
        None => KetError::DataNotAvailable.error_code(),
    }
}

#[no_mangle]
pub unsafe extern "C" fn ket_process_set_serialized_result(
    process: &mut Process,
    result: *const u8,
    result_size: usize,
    data_type: i32,
) -> i32 {
    let data_type = match DataType::from_i32(data_type) {
        Some(data_type) => data_type,
        None => return KetError::UndefinedDataType.error_code(),
    };

    let result = unsafe { std::slice::from_raw_parts(result, result_size) };

    let result = match data_type {
        DataType::JSON => SerializedData::JSON(String::from(std::str::from_utf8(result).unwrap())),
        DataType::BIN => SerializedData::BIN(result.to_vec()),
    };

    wrapper(process.set_serialized_result(&result))
}

#[no_mangle]
pub extern "C" fn ket_features_new(
    allow_dirty_qubits: bool,
    allow_free_qubits: bool,
    valid_after_measure: bool,
    classical_control_flow: bool,
    allow_dump: bool,
    allow_measure: bool,
    continue_after_dump: bool,
    features: &mut *mut Features,
) -> i32 {
    *features = Box::into_raw(Box::new(Features::new(
        allow_dirty_qubits,
        allow_free_qubits,
        valid_after_measure,
        classical_control_flow,
        allow_dump,
        allow_measure,
        continue_after_dump,
    )));
    KetError::Success.error_code()
}

#[no_mangle]
pub unsafe extern "C" fn ket_features_delete(features: *mut Features) -> i32 {
    unsafe { Box::from_raw(features) };
    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_features_all(features: &mut *mut Features) -> i32 {
    *features = Box::into_raw(Box::new(Features::all()));
    KetError::Success.error_code()
}

#[no_mangle]
pub extern "C" fn ket_features_none(features: &mut *mut Features) -> i32 {
    *features = Box::into_raw(Box::new(Features::none()));
    KetError::Success.error_code()
}

#[no_mangle]
pub unsafe extern "C" fn ket_features_register_plugin(
    features: &mut Features,
    name: *const c_char,
) -> i32 {
    let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
    features.register_plugin(String::from(name));
    KetError::Success.error_code()
}
