// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

use googletest::prelude::*;
use map_unittest_proto::TestMap;
use paste::paste;
use std::collections::HashMap;

macro_rules! generate_map_primitives_tests {
    (
        $(($k_type:ty, $v_type:ty, $k_field:ident, $v_field:ident,
           $k_nonzero:expr, $v_nonzero:expr $(,)?)),*
        $(,)?
    ) => {
        paste! { $(
            #[test]
            fn [< test_map_ $k_field _ $v_field >]() {
                let mut msg = TestMap::new();
                assert_that!(msg.[< map_ $k_field _ $v_field >]().len(), eq(0));
                assert_that!(
                    msg.[< map_ $k_field _ $v_field >]().iter().collect::<Vec<_>>(),
                    elements_are![]
                );
                let k = <$k_type>::default();
                let v = <$v_type>::default();
                assert_that!(msg.[< map_ $k_field _ $v_field _mut>]().insert(k, v), eq(true));
                assert_that!(msg.[< map_ $k_field _ $v_field _mut>]().insert(k, v), eq(false));
                assert_that!(msg.[< map_ $k_field _ $v_field >]().len(), eq(1));
                assert_that!(
                    msg.[< map_ $k_field _ $v_field >]().iter().collect::<Vec<_>>(),
                    elements_are![eq((k, v))]
                );

                let k: $k_type = $k_nonzero;
                let v: $v_type = $v_nonzero;
                assert_that!(msg.[< map_ $k_field _ $v_field _mut>]().insert(k, v), eq(true));
                assert_that!(msg.[< map_ $k_field _ $v_field >]().len(), eq(2));
                assert_that!(
                    msg.[< map_ $k_field _ $v_field >]().iter().collect::<Vec<_>>(),
                    unordered_elements_are![
                        eq((k, v)),
                        eq((<$k_type>::default(), <$v_type>::default())),
                    ]
                );
            }
        )* }
    };
}

generate_map_primitives_tests!(
    (i32, i32, int32, int32, 1, 1),
    (i64, i64, int64, int64, 1, 1),
    (u32, u32, uint32, uint32, 1, 1),
    (u64, u64, uint64, uint64, 1, 1),
    (i32, i32, sint32, sint32, 1, 1),
    (i64, i64, sint64, sint64, 1, 1),
    (u32, u32, fixed32, fixed32, 1, 1),
    (u64, u64, fixed64, fixed64, 1, 1),
    (i32, i32, sfixed32, sfixed32, 1, 1),
    (i64, i64, sfixed64, sfixed64, 1, 1),
    (i32, f32, int32, float, 1, 1.),
    (i32, f64, int32, double, 1, 1.),
    (bool, bool, bool, bool, true, true),
    (i32, &[u8], int32, bytes, 1, b"foo"),
);

#[test]
fn collect_as_hashmap() {
    // Highlights conversion from protobuf map to hashmap.
    let mut msg = TestMap::new();
    msg.map_string_string_mut().insert("hello", "world");
    msg.map_string_string_mut().insert("fizz", "buzz");
    msg.map_string_string_mut().insert("boo", "blah");
    let hashmap: HashMap<String, String> =
        msg.map_string_string().iter().map(|(k, v)| (k.to_string(), v.to_string())).collect();
    assert_that!(
        hashmap.into_iter().collect::<Vec<_>>(),
        unordered_elements_are![
            eq(("hello".to_owned(), "world".to_owned())),
            eq(("fizz".to_owned(), "buzz".to_owned())),
            eq(("boo".to_owned(), "blah".to_owned())),
        ]
    );
}

#[test]
fn test_string_maps() {
    let mut msg = TestMap::new();
    msg.map_string_string_mut().insert("hello", "world");
    msg.map_string_string_mut().insert("fizz", "buzz");
    assert_that!(msg.map_string_string().len(), eq(2));
    assert_that!(msg.map_string_string().get("fizz").unwrap(), eq("buzz"));
    assert_that!(msg.map_string_string().get("not found"), eq(None));
    msg.map_string_string_mut().clear();
    assert_that!(msg.map_string_string().len(), eq(0));
}

#[test]
fn test_bytes_and_string_copied() {
    let mut msg = TestMap::new();

    {
        // Ensure val is dropped after inserting into the map.
        let key = String::from("hello");
        let val = String::from("world");
        msg.map_string_string_mut().insert(key.as_str(), val.as_str());
        msg.map_int32_bytes_mut().insert(1, val.as_bytes());
    }

    assert_that!(msg.map_string_string_mut().get("hello").unwrap(), eq("world"));
    assert_that!(msg.map_int32_bytes_mut().get(1).unwrap(), eq(b"world"));
}