use super::*;
use std::ffi::CString;
use std::mem;
extern crate libc;

unsafe fn to_c_pointer_array<T, S>(vec: &[T], convert_fun: impl Fn(&T) -> *mut S) -> *mut *mut S {
    let size = vec.len() * mem::size_of::<*mut std::os::raw::c_void>();
    let ptr = unsafe { libc::malloc(size) as *mut *mut S };
    for (i, el) in vec.iter().enumerate() {
        unsafe { *ptr.add(i) = convert_fun(el) };
    }
    ptr
}

unsafe fn to_array_of_strings(vec: &[String]) -> *mut *mut bindings::mg_string {
    unsafe {
        to_c_pointer_array(vec, |el| {
            let c_str = CString::new(el.as_str()).unwrap();
            bindings::mg_string_make(c_str.as_ptr())
        })
    }
}

unsafe fn to_array_of_nodes(vec: &[Node]) -> *mut *mut bindings::mg_node {
    unsafe {
        to_c_pointer_array(vec, |el| {
            bindings::mg_node_copy(&bindings::mg_node {
                id: el.id,
                label_count: el.label_count,
                labels: to_array_of_strings(&el.labels),
                properties: hash_map_to_mg_map(&el.properties),
            })
        })
    }
}

unsafe fn to_array_of_unbound_relationships(
    vec: &[UnboundRelationship],
) -> *mut *mut bindings::mg_unbound_relationship {
    unsafe {
        to_c_pointer_array(vec, |x| {
            let c_type = CString::new(x.type_.as_str()).unwrap();
            bindings::mg_unbound_relationship_copy(&bindings::mg_unbound_relationship {
                id: x.id,
                type_: bindings::mg_string_make(c_type.as_ptr()),
                properties: hash_map_to_mg_map(&x.properties),
            })
        })
    }
}

unsafe fn to_c_int_array(vec: &[i64]) -> *mut i64 {
    let size = mem::size_of_val(vec);
    let ptr = unsafe { libc::malloc(size) as *mut i64 };
    for (i, el) in vec.iter().enumerate() {
        unsafe { *ptr.add(i) = *el };
    }
    ptr
}

fn mg_value_to_c_mg_value(mg_value: &Value) -> *mut bindings::mg_value {
    unsafe {
        match mg_value {
            Value::Null => bindings::mg_value_make_null(),
            Value::Bool(x) => bindings::mg_value_make_bool(match x {
                false => 0,
                true => 1,
            }),
            Value::Int(x) => bindings::mg_value_make_integer(*x),
            Value::Float(x) => bindings::mg_value_make_float(*x),
            Value::String(x) => {
                let c_str = CString::new(x.as_str()).unwrap();
                bindings::mg_value_make_string(c_str.as_ptr())
            }
            Value::Date(x) => bindings::mg_value_make_date(date_to_mg_date(x)),
            Value::LocalTime(x) => {
                bindings::mg_value_make_local_time(local_time_to_mg_local_time(x))
            }
            Value::LocalDateTime(x) => {
                bindings::mg_value_make_local_date_time(local_date_time_to_mg_local_date_time(x))
            }
            Value::DateTime(_x) => {
                // TODO: Implement conversion from DateTime to mg_value
                // For now, we'll create a null value as placeholder
                bindings::mg_value_make_null()
            }
            Value::Duration(x) => bindings::mg_value_make_duration(duration_to_mg_duration(x)),
            Value::Point2D(x) => bindings::mg_value_make_point_2d(point2d_to_mg_point_2d(x)),
            Value::Point3D(x) => bindings::mg_value_make_point_3d(point3d_to_mg_point_3d(x)),
            Value::List(x) => {
                bindings::mg_value_make_list(bindings::mg_list_copy(vector_to_mg_list(x)))
            }
            Value::Map(x) => {
                bindings::mg_value_make_map(bindings::mg_map_copy(hash_map_to_mg_map(x)))
            }
            Value::Node(x) => {
                let labels_box = to_array_of_strings(&x.labels);
                let c_node = bindings::mg_node {
                    id: x.id,
                    label_count: x.label_count,
                    labels: labels_box,
                    properties: hash_map_to_mg_map(&x.properties),
                };
                bindings::mg_value_make_node(bindings::mg_node_copy(&c_node))
            }
            Value::Relationship(x) => {
                let c_type_str = CString::new(x.type_.as_str()).unwrap();
                let c_type = bindings::mg_string_make(c_type_str.as_ptr());
                let c_relationship2 = bindings::mg_relationship {
                    id: x.id,
                    start_id: x.start_id,
                    end_id: x.end_id,
                    type_: c_type,
                    properties: hash_map_to_mg_map(&x.properties),
                };
                bindings::mg_value_make_relationship(bindings::mg_relationship_copy(
                    &c_relationship2,
                ))
            }
            Value::UnboundRelationship(x) => {
                let c_type_str = CString::new(x.type_.as_str()).unwrap();
                let c_type = bindings::mg_string_make(c_type_str.as_ptr());
                let c_unbound_relationship = bindings::mg_unbound_relationship {
                    id: x.id,
                    type_: c_type,
                    properties: hash_map_to_mg_map(&x.properties),
                };
                bindings::mg_value_make_unbound_relationship(
                    bindings::mg_unbound_relationship_copy(&c_unbound_relationship),
                )
            }
            Value::Path(x) => {
                let mut sequence = Vec::new();
                for i in 0..x.node_count {
                    sequence.push(x.nodes[i as usize].id);
                    if i < x.relationship_count {
                        sequence.push(x.relationships[i as usize].id);
                    }
                }
                let sequence_length = sequence.len() as u32;
                let seq_ptr = to_c_int_array(&sequence);

                let nodes_box = to_array_of_nodes(&x.nodes);
                let unbound_relationship_box = to_array_of_unbound_relationships(&x.relationships);
                let c_path = bindings::mg_path {
                    node_count: x.node_count,
                    relationship_count: x.relationship_count,
                    sequence_length,
                    nodes: nodes_box,
                    relationships: unbound_relationship_box,
                    sequence: seq_ptr,
                };
                bindings::mg_value_make_path(bindings::mg_path_copy(&c_path))
            }
        }
    }
}
fn vector_to_mg_list(vector: &[Value]) -> *mut bindings::mg_list {
    let size = vector.len() as u32;
    let mg_list = unsafe { bindings::mg_list_make_empty(size) };
    if mg_list.is_null() {
        panic!("Failed to allocate mg_list in test");
    }
    for mg_val in vector {
        let mg_value = mg_value_to_c_mg_value(mg_val);
        if mg_value.is_null() {
            unsafe { bindings::mg_list_destroy(mg_list) };
            panic!("Failed to allocate mg_value in test");
        }
        unsafe {
            bindings::mg_list_append(mg_list, mg_value);
        };
    }
    mg_list
}

fn hash_map_to_mg_map(hash_map: &HashMap<String, Value>) -> *mut bindings::mg_map {
    let size = hash_map.len();
    let mg_map = unsafe { bindings::mg_map_make_empty(size as u32) };
    if mg_map.is_null() {
        panic!("Failed to allocate mg_map in test");
    }
    for (key, val) in hash_map {
        let c_key = CString::new(key.as_str()).unwrap();
        let mg_value = mg_value_to_c_mg_value(val);
        if mg_value.is_null() {
            unsafe { bindings::mg_map_destroy(mg_map) };
            panic!("Failed to allocate mg_value in test");
        }
        unsafe {
            bindings::mg_map_insert(mg_map, c_key.as_ptr(), mg_value);
        }
    }

    mg_map
}

#[test]
fn from_c_mg_value_null() {
    let c_mg_value = unsafe { bindings::mg_value_make_null() };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Null, mg_value);
    assert_eq!(format!("{}", mg_value), "NULL");
}

#[test]
fn from_c_mg_value_null_display() {
    let c_mg_value = unsafe { bindings::mg_value_make_null() };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(format!("{}", mg_value), "NULL");
}

#[test]
fn from_c_mg_value_bool_false() {
    let c_mg_value = unsafe { bindings::mg_value_make_bool(0) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Bool(false), mg_value);
    assert_eq!(format!("{}", mg_value), "false");
}

#[test]
fn from_c_mg_value_bool_false_display() {
    let c_mg_value = unsafe { bindings::mg_value_make_bool(0) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(format!("{}", mg_value), "false");
}

#[test]
fn from_c_mg_value_bool_true() {
    let c_mg_value = unsafe { bindings::mg_value_make_bool(27) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Bool(true), mg_value);
}

#[test]
fn from_c_mg_value_int() {
    let c_mg_value = unsafe { bindings::mg_value_make_integer(19) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Int(19), mg_value);
    assert_eq!(format!("{}", mg_value), "19");
}

#[test]
fn from_c_mg_value_int_display() {
    let c_mg_value = unsafe { bindings::mg_value_make_integer(19) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(format!("{}", mg_value), "19");
}

#[test]
fn from_c_mg_value_float() {
    let c_mg_value = unsafe { bindings::mg_value_make_float(3.1465) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Float(3.1465), mg_value);
    assert_eq!(format!("{}", mg_value), "3.1465");
}

#[test]
fn from_c_mg_value_float_display() {
    let c_mg_value = unsafe { bindings::mg_value_make_float(3.1465) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(format!("{}", mg_value), "3.1465");
}

#[test]
fn from_c_mg_value_string() {
    let c_str = CString::new(String::from("ṰⱻⱾᵀ")).unwrap();
    let c_mg_value = unsafe { bindings::mg_value_make_string(c_str.as_ptr()) };

    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::String(String::from("ṰⱻⱾᵀ")), mg_value);
    assert_eq!(format!("{}", mg_value), "\'ṰⱻⱾᵀ\'");
}

#[test]
fn from_c_mg_value_date1() {
    let c_date = bindings::mg_date { days: 100 };
    let c_mg_value = unsafe { bindings::mg_value_make_date(bindings::mg_date_copy(&c_date)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Date(Date::new(1970, 4, 11).unwrap()), mg_value);
    assert_eq!(format!("{}", mg_value), "'1970-04-11'");
}

#[test]
fn from_c_mg_value_date2() {
    let c_date = bindings::mg_date { days: 365 };
    let c_mg_value = unsafe { bindings::mg_value_make_date(bindings::mg_date_copy(&c_date)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Date(Date::new(1971, 1, 1).unwrap()), mg_value);
    assert_eq!(format!("{}", mg_value), "'1971-01-01'");
}

#[test]
fn from_c_mg_value_date3() {
    let c_date = bindings::mg_date { days: -365 };
    let c_mg_value = unsafe { bindings::mg_value_make_date(bindings::mg_date_copy(&c_date)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Date(Date::new(1969, 1, 1).unwrap()), mg_value);
    assert_eq!(format!("{}", mg_value), "'1969-01-01'");
}

#[test]
fn from_c_mg_value_local_time() {
    let c_local_time = bindings::mg_local_time {
        nanoseconds: 52835851241000,
    };
    let c_mg_value =
        unsafe { bindings::mg_value_make_local_time(bindings::mg_local_time_copy(&c_local_time)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::LocalTime(LocalTime::new(14, 40, 35, 851_241_000).unwrap()),
        mg_value
    );
    assert_eq!(format!("{}", mg_value), "'14:40:35.851241'");
}

#[test]
fn from_c_mg_value_local_date_time1() {
    let c_local_date_time = bindings::mg_local_date_time {
        seconds: 500 * 24 * 60 * 60 + 52835,
        nanoseconds: 851241 * 1000,
    };
    let c_mg_value = unsafe {
        bindings::mg_value_make_local_date_time(bindings::mg_local_date_time_copy(
            &c_local_date_time,
        ))
    };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::LocalDateTime(LocalDateTime::new(1971, 5, 16, 14, 40, 35, 851_241_000).unwrap()),
        mg_value
    );
    assert_eq!(format!("{}", mg_value), "'1971-05-16 14:40:35.851241'");
}

#[test]
fn from_c_mg_value_local_date_time2() {
    let c_local_date_time = bindings::mg_local_date_time {
        seconds: -500 * 24 * 60 * 60 + 52835,
        nanoseconds: 851241 * 1000,
    };
    let c_mg_value = unsafe {
        bindings::mg_value_make_local_date_time(bindings::mg_local_date_time_copy(
            &c_local_date_time,
        ))
    };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::LocalDateTime(LocalDateTime::new(1968, 8, 19, 14, 40, 35, 851_241_000).unwrap()),
        mg_value
    );
    assert_eq!(format!("{}", mg_value), "'1968-08-19 14:40:35.851241'");
}

#[test]
fn from_c_mg_value_local_date_time3() {
    let c_local_date_time = bindings::mg_local_date_time {
        seconds: -1,
        nanoseconds: 0,
    };
    let c_mg_value = unsafe {
        bindings::mg_value_make_local_date_time(bindings::mg_local_date_time_copy(
            &c_local_date_time,
        ))
    };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::LocalDateTime(LocalDateTime::new(1969, 12, 31, 23, 59, 59, 0).unwrap()),
        mg_value
    );
    assert_eq!(format!("{}", mg_value), "'1969-12-31 23:59:59'");
}

#[test]
fn from_c_mg_value_duration() {
    let c_duration = bindings::mg_duration {
        months: 0,
        days: 10,
        seconds: 100,
        nanoseconds: 1000,
    };
    let c_mg_value =
        unsafe { bindings::mg_value_make_duration(bindings::mg_duration_copy(&c_duration)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::Duration(Duration::days(10) + Duration::seconds(100) + Duration::nanoseconds(1000)),
        mg_value
    );
    assert_eq!(format!("{}", mg_value), "'PT864100.000001S'");
}

#[test]
fn from_c_mg_value_point_2d() {
    let c_point2d = bindings::mg_point_2d {
        srid: 0,
        x: 1.0,
        y: 2.0,
    };
    let c_mg_value =
        unsafe { bindings::mg_value_make_point_2d(bindings::mg_point_2d_copy(&c_point2d)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::Point2D(Point2D {
            srid: 0,
            x_longitude: 1.0,
            y_latitude: 2.0
        }),
        mg_value
    );
    assert_eq!(format!("{}", mg_value), "'Point2D({ srid:0, x:1, y:2 })'");
}

#[test]
fn from_c_mg_value_point_3d() {
    let c_point3d = bindings::mg_point_3d {
        srid: 0,
        x: 1.0,
        y: 2.0,
        z: 3.0,
    };
    let c_mg_value =
        unsafe { bindings::mg_value_make_point_3d(bindings::mg_point_3d_copy(&c_point3d)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(
        Value::Point3D(Point3D {
            srid: 0,
            x_longitude: 1.0,
            y_latitude: 2.0,
            z_height: 3.0
        }),
        mg_value
    );
    assert_eq!(
        format!("{}", mg_value),
        "'Point3D({ srid:0, x:1, y:2, z:3 })'"
    );
}

#[test]
fn from_c_mg_value_list() {
    let mg_values = vec![
        Value::Null,
        Value::Bool(true),
        Value::Int(130),
        Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: "test".to_string(),
            properties: hashmap! {
                String::from("name") => Value::Null,
            },
        }),
    ];

    let c_mg_value = unsafe { bindings::mg_value_make_list(vector_to_mg_list(&mg_values)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::List(mg_values), mg_value);
}

#[test]
fn from_c_mg_value_list_display() {
    let mg_value = Value::List(vec![
        Value::Null,
        Value::Bool(true),
        Value::Int(130),
        Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: "test".to_string(),
            properties: hashmap! {
                String::from("name") => Value::Null,
            },
        }),
    ]);

    assert_eq!(
        format!("{}", mg_value),
        "NULL, true, 130, [:test {'name': NULL}]"
    );
}

#[test]
fn from_c_mg_value_map() {
    let mg_map = hashmap! {
        String::from("name") => Value::Null,
        String::from("is_it") => Value::Bool(true),
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };

    let c_mg_value = unsafe { bindings::mg_value_make_map(hash_map_to_mg_map(&mg_map)) };
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(Value::Map(mg_map), mg_value);

    assert_eq!(
        format!("{}", mg_value),
        "{'id': 128, 'is_it': true, 'name': NULL, 'rel': [:test {'name': NULL}]}"
    );
}

#[test]
fn from_c_mg_value_map_display() {
    let mg_map = Value::Map(hashmap! {
        String::from("name") => Value::Null,
        String::from("is_it") => Value::Bool(true),
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    });

    assert_eq!(
        format!("{}", mg_map),
        "{'id': 128, 'is_it': true, 'name': NULL, 'rel': [:test {'name': NULL}]}"
    );
}

#[test]
fn from_c_mg_value_node() {
    let values = vec![String::from("test")];
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_node = Value::Node(Node {
        id: 1,
        label_count: 1,
        labels: values,
        properties: mg_map,
    });

    let c_mg_value = mg_value_to_c_mg_value(&c_node);
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(c_node, mg_value);

    //assert_eq!(format!("{}",value),"{'id': 128, 'is_it': true, 'name': NULL, 'rel': [:test {'name': NULL}]}");
}

#[test]
fn from_c_mg_value_node_display() {
    let values = vec![String::from("test")];
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_node = Value::Node(Node {
        id: 1,
        label_count: 1,
        labels: values,
        properties: mg_map,
    });

    assert_eq!(
        format!("{}", c_node),
        "(:test {'id': 128, 'rel': [:test {'name': NULL}]})"
    );
}

#[test]
fn from_c_mg_value_relationship() {
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_relationship = Value::Relationship(Relationship {
        id: 1,
        start_id: 1,
        end_id: 2,
        type_: String::from("test"),
        properties: mg_map,
    });

    let c_mg_value = mg_value_to_c_mg_value(&c_relationship);
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(c_relationship, mg_value);
}

#[test]
fn from_c_mg_value_relationship_display() {
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_relationship = Value::Relationship(Relationship {
        id: 1,
        start_id: 1,
        end_id: 2,
        type_: String::from("test"),
        properties: mg_map,
    });

    assert_eq!(
        format!("{}", c_relationship),
        "[:test {'id': 128, 'rel': [:test {'name': NULL}]}]"
    );
}

#[test]
fn from_c_mg_value_unbound_relationship() {
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_unbound_relationship = Value::UnboundRelationship(UnboundRelationship {
        id: 1,
        type_: String::from("test"),
        properties: mg_map,
    });

    let c_mg_value = mg_value_to_c_mg_value(&c_unbound_relationship);
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(c_unbound_relationship, mg_value);
}

#[test]
fn from_c_mg_value_unbound_relationship_display() {
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_unbound_relationship = Value::UnboundRelationship(UnboundRelationship {
        id: 1,
        type_: String::from("test"),
        properties: mg_map,
    });
    assert_eq!(
        format!("{}", c_unbound_relationship),
        "[:test {'id': 128, 'rel': [:test {'name': NULL}]}]"
    );
}

#[test]
fn from_c_mg_value_path() {
    let values = vec![String::from("test")];
    let values2 = vec![String::from("test")];
    let mg_map = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_unbound_relationship = UnboundRelationship {
        id: 1,
        type_: String::from("test"),
        properties: mg_map,
    };
    let mg_map2 = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let mg_map3 = hashmap! {
        String::from("id") => Value::Int(128),
        String::from("rel") => Value::Relationship(Relationship {
            id: 1,
            start_id: 1,
            end_id: 2,
            type_: String::from("test"),
            properties: hashmap!{
                String::from("name") => Value::Null,
            }
        }),
    };
    let c_node = Node {
        id: 1,
        label_count: 1,
        labels: values,
        properties: mg_map2,
    };
    let c_node2 = Node {
        id: 1,
        label_count: 1,
        labels: values2,
        properties: mg_map3,
    };
    let c_path = Value::Path(Path {
        node_count: 2,
        relationship_count: 1,
        nodes: vec![c_node, c_node2],
        relationships: vec![c_unbound_relationship],
    });

    let c_mg_value = mg_value_to_c_mg_value(&c_path);
    let mg_value = unsafe { Value::from_mg_value(c_mg_value) };
    assert_eq!(c_path, mg_value);
}

#[test]
fn from_to_c_mg_value_null() {
    let query_param_null = QueryParam::Null;
    let c_mg_value = unsafe { *(query_param_null.to_c_mg_value()) };
    assert_eq!(c_mg_value.type_, bindings::mg_value_type_MG_VALUE_TYPE_NULL);
}

#[test]
fn from_to_c_mg_value_false() {
    let query_param_false = QueryParam::Bool(false);
    let c_mg_value = unsafe { *(query_param_false.to_c_mg_value()) };
    assert_eq!(c_mg_value.type_, bindings::mg_value_type_MG_VALUE_TYPE_BOOL);
}

#[test]
fn from_to_c_mg_value_true() {
    let query_param_true = QueryParam::Bool(true);
    let c_mg_value = unsafe { *(query_param_true.to_c_mg_value()) };
    assert_eq!(c_mg_value.type_, bindings::mg_value_type_MG_VALUE_TYPE_BOOL);
}

#[test]
fn from_to_c_mg_value_int() {
    let query_param_int = QueryParam::Int(20);
    let c_mg_value = unsafe { *(query_param_int.to_c_mg_value()) };
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_INTEGER
    );
    assert_eq!(mg_value.to_string(), "20");
}

#[test]
fn from_to_c_mg_value_float() {
    let query_param_float = QueryParam::Float(3.15);
    let c_mg_value = unsafe { *(query_param_float.to_c_mg_value()) };
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_FLOAT
    );
    assert_eq!(mg_value.to_string(), "3.15");
}

#[test]
fn from_to_c_mg_value_string() {
    let query_param = QueryParam::String("test".to_string());
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_STRING
    );
    assert_eq!(
        unsafe { mg_string_to_string(c_mg_value.__bindgen_anon_1.string_v) },
        String::from("test")
    );
}

#[test]
fn from_naive_date_param_to_mg_value() {
    let query_param = QueryParam::Date(Date::new(1971, 1, 1).unwrap());
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(c_mg_value.type_, bindings::mg_value_type_MG_VALUE_TYPE_DATE);
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(mg_value.to_string(), "'1971-01-01'");
    match mg_value {
        Value::Date(x) => {
            assert_eq!(x.year(), 1971);
            assert_eq!(x.month(), 1);
            assert_eq!(x.day(), 1);
        }
        _ => {
            panic!("QueryParam::Date converted into a wrong Value type!");
        }
    }
}

#[test]
fn from_naive_local_time_param_to_mg_value() {
    let query_param = QueryParam::LocalTime(LocalTime::new(2, 3, 4, 1234).unwrap());
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_LOCAL_TIME
    );
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(mg_value.to_string(), "'02:03:04.000001234'");
    match mg_value {
        Value::LocalTime(x) => {
            assert_eq!(x.hour(), 2);
            assert_eq!(x.minute(), 3);
            assert_eq!(x.second(), 4);
            assert_eq!(x.nanosecond(), 1234);
        }
        _ => {
            panic!("QueryParam::LocalTime converted into a wrong Value type!");
        }
    }
}

#[test]
fn from_naive_local_date_time_param_to_mg_value() {
    let query_param =
        QueryParam::LocalDateTime(LocalDateTime::new(1960, 1, 1, 2, 3, 4, 1234).unwrap());
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_LOCAL_DATE_TIME
    );
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(mg_value.to_string(), "'1960-01-01 02:03:04.000001234'");
    match mg_value {
        Value::LocalDateTime(x) => {
            assert_eq!(x.year(), 1960);
            assert_eq!(x.month(), 1);
            assert_eq!(x.day(), 1);
            assert_eq!(x.hour(), 2);
            assert_eq!(x.minute(), 3);
            assert_eq!(x.second(), 4);
            assert_eq!(x.nanosecond(), 1234);
        }
        _ => {
            panic!("QueryParam::LocalDateTime converted into a wrong Value type!");
        }
    }
}

#[test]
fn from_duration_to_mg_value_1() {
    let query_param = QueryParam::Duration(Duration::nanoseconds(86403000000000));
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_DURATION
    );
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(mg_value.to_string(), "'PT86403S'");
    match mg_value {
        Value::Duration(x) => {
            assert_eq!(x.num_weeks(), 0);
            assert_eq!(x.num_hours(), 24);
            assert_eq!(x.num_days(), 1);
            assert_eq!(x.num_seconds(), 86403);
            assert_eq!(x.num_nanoseconds(), 86403000000000);
        }
        _ => {
            panic!("QueryParam::Duration converted into a wrong Value type!");
        }
    }
}

#[test]
fn from_duration_to_mg_value_2() {
    let query_param = QueryParam::Duration(Duration::nanoseconds(123456789));
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_DURATION
    );
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(mg_value.to_string(), "'PT0.123456789S'");
    match mg_value {
        Value::Duration(x) => {
            assert_eq!(x.num_weeks(), 0);
            assert_eq!(x.num_hours(), 0);
            assert_eq!(x.num_days(), 0);
            assert_eq!(x.num_seconds(), 0);
            assert_eq!(x.num_nanoseconds(), 123456789);
        }
        _ => {
            panic!("QueryParam::Duration converted into a wrong Value type!");
        }
    }
}

#[test]
fn from_point2d_to_mg_point_2d() {
    let query_param = QueryParam::Point2D(Point2D {
        srid: 0,
        x_longitude: 1.0,
        y_latitude: 2.0,
    });
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_POINT_2D
    );
}

#[test]
fn from_point3d_to_mg_point_3d() {
    let query_param = QueryParam::Point3D(Point3D {
        srid: 0,
        x_longitude: 1.0,
        y_latitude: 2.0,
        z_height: 3.0,
    });
    let c_mg_value = unsafe { *(query_param.to_c_mg_value()) };
    assert_eq!(
        c_mg_value.type_,
        bindings::mg_value_type_MG_VALUE_TYPE_POINT_3D
    );
}

#[test]
fn from_to_c_mg_value_list() {
    let vec: Vec<QueryParam> = vec![
        QueryParam::Null,
        QueryParam::Bool(true),
        QueryParam::Int(20),
        QueryParam::Float(3.15),
    ];
    let query_param_list = QueryParam::List(vec);
    let c_mg_value = unsafe { *(query_param_list.to_c_mg_value()) };
    let mg_value = unsafe { Value::from_mg_value(&c_mg_value) };
    assert_eq!(c_mg_value.type_, bindings::mg_value_type_MG_VALUE_TYPE_LIST);
    let _c_mg_list = mg_value_to_c_mg_value(&mg_value);
}

#[test]
fn from_to_c_mg_value_map() {
    let mut map: HashMap<String, QueryParam> = HashMap::new();
    map.insert("null".to_string(), QueryParam::Null);
    map.insert("true".to_string(), QueryParam::Bool(true));
    map.insert("int".to_string(), QueryParam::Int(20));
    map.insert("float".to_string(), QueryParam::Float(3.15));
    let query_param_map = QueryParam::Map(map);
    let c_mg_value = unsafe { *(query_param_map.to_c_mg_value()) };
    let _mg_value = unsafe { Value::from_mg_value(&c_mg_value) };

    assert_eq!(c_mg_value.type_, bindings::mg_value_type_MG_VALUE_TYPE_MAP);
}

#[test]
fn from_c_mg_value_unknown() {
    let c_mg_value = bindings::mg_value {
        type_: bindings::mg_value_type_MG_VALUE_TYPE_UNKNOWN,
        __bindgen_anon_1: bindings::mg_value__bindgen_ty_1 { bool_v: 0 },
    };
    let _mg_value = unsafe { Value::from_mg_value(&c_mg_value as *const bindings::mg_value) };
    unsafe {
        assert_eq!(
            c_mg_value.type_,
            bindings::mg_value_get_type(&c_mg_value as *const bindings::mg_value)
        )
    };
}
