#include <furi.h>
#include "../minunit.h"

static void test_setup(void) {
}

static void test_teardown(void) {
}

static FuriString* furi_string_alloc_vprintf_test(const char format[], ...) {
    va_list args;
    va_start(args, format);
    FuriString* string = furi_string_alloc_vprintf(format, args);
    va_end(args);
    return string;
}

MU_TEST(mu_test_furi_string_alloc_free) {
    FuriString* tmp;
    FuriString* string;

    // test alloc and free
    string = furi_string_alloc();
    mu_check(string != NULL);
    mu_check(furi_string_empty(string));
    furi_string_free(string);

    // test furi_string_alloc_set_str and free
    string = furi_string_alloc_set_str("test");
    mu_check(string != NULL);
    mu_check(!furi_string_empty(string));
    mu_check(furi_string_cmp(string, "test") == 0);
    furi_string_free(string);

    // test furi_string_alloc_set and free
    tmp = furi_string_alloc_set("more");
    string = furi_string_alloc_set(tmp);
    furi_string_free(tmp);
    mu_check(string != NULL);
    mu_check(!furi_string_empty(string));
    mu_check(furi_string_cmp(string, "more") == 0);
    furi_string_free(string);

    // test alloc_printf and free
    string = furi_string_alloc_printf("test %d %s %c 0x%02x", 1, "two", '3', 0x04);
    mu_check(string != NULL);
    mu_check(!furi_string_empty(string));
    mu_check(furi_string_cmp(string, "test 1 two 3 0x04") == 0);
    furi_string_free(string);

    // test alloc_vprintf and free
    string = furi_string_alloc_vprintf_test("test %d %s %c 0x%02x", 4, "five", '6', 0x07);
    mu_check(string != NULL);
    mu_check(!furi_string_empty(string));
    mu_check(furi_string_cmp(string, "test 4 five 6 0x07") == 0);
    furi_string_free(string);

    // test alloc_move and free
    tmp = furi_string_alloc_set("move");
    string = furi_string_alloc_move(tmp);
    mu_check(string != NULL);
    mu_check(!furi_string_empty(string));
    mu_check(furi_string_cmp(string, "move") == 0);
    furi_string_free(string);
}

MU_TEST(mu_test_furi_string_mem) {
    FuriString* string = furi_string_alloc_set("test");
    mu_check(string != NULL);
    mu_check(!furi_string_empty(string));

    // TODO: how to test furi_string_reserve?

    // test furi_string_reset
    furi_string_reset(string);
    mu_check(furi_string_empty(string));

    // test furi_string_swap
    furi_string_set(string, "test");
    FuriString* swap_string = furi_string_alloc_set("swap");
    furi_string_swap(string, swap_string);
    mu_check(furi_string_cmp(string, "swap") == 0);
    mu_check(furi_string_cmp(swap_string, "test") == 0);
    furi_string_free(swap_string);

    // test furi_string_move
    FuriString* move_string = furi_string_alloc_set("move");
    furi_string_move(string, move_string);
    mu_check(furi_string_cmp(string, "move") == 0);
    // move_string is now empty
    // and tested by leaked memory check at the end of the tests

    furi_string_set(string, "abracadabra");

    // test furi_string_hash
    mu_assert_int_eq(0xc3bc16d7, furi_string_hash(string));

    // test furi_string_size
    mu_assert_int_eq(11, furi_string_size(string));

    // test furi_string_empty
    mu_check(!furi_string_empty(string));
    furi_string_reset(string);
    mu_check(furi_string_empty(string));

    furi_string_free(string);
}

MU_TEST(mu_test_furi_string_getters) {
    FuriString* string = furi_string_alloc_set("test");

    // test furi_string_get_char
    mu_check(furi_string_get_char(string, 0) == 't');
    mu_check(furi_string_get_char(string, 1) == 'e');
    mu_check(furi_string_get_char(string, 2) == 's');
    mu_check(furi_string_get_char(string, 3) == 't');

    // test furi_string_get_cstr
    mu_assert_string_eq("test", furi_string_get_cstr(string));
    furi_string_free(string);
}

static FuriString* furi_string_vprintf_test(FuriString* string, const char format[], ...) {
    va_list args;
    va_start(args, format);
    furi_string_vprintf(string, format, args);
    va_end(args);
    return string;
}

MU_TEST(mu_test_furi_string_setters) {
    FuriString* tmp;
    FuriString* string = furi_string_alloc();

    // test furi_string_set_str
    furi_string_set_str(string, "test");
    mu_assert_string_eq("test", furi_string_get_cstr(string));

    // test furi_string_set
    tmp = furi_string_alloc_set("more");
    furi_string_set(string, tmp);
    furi_string_free(tmp);
    mu_assert_string_eq("more", furi_string_get_cstr(string));

    // test furi_string_set_strn
    furi_string_set_strn(string, "test", 2);
    mu_assert_string_eq("te", furi_string_get_cstr(string));

    // test furi_string_set_char
    furi_string_set_char(string, 0, 'a');
    furi_string_set_char(string, 1, 'b');
    mu_assert_string_eq("ab", furi_string_get_cstr(string));

    // test furi_string_set_n
    tmp = furi_string_alloc_set("dodecahedron");
    furi_string_set_n(string, tmp, 4, 5);
    furi_string_free(tmp);
    mu_assert_string_eq("cahed", furi_string_get_cstr(string));

    // test furi_string_printf
    furi_string_printf(string, "test %d %s %c 0x%02x", 1, "two", '3', 0x04);
    mu_assert_string_eq("test 1 two 3 0x04", furi_string_get_cstr(string));

    // test furi_string_vprintf
    furi_string_vprintf_test(string, "test %d %s %c 0x%02x", 4, "five", '6', 0x07);
    mu_assert_string_eq("test 4 five 6 0x07", furi_string_get_cstr(string));

    furi_string_free(string);
}

static FuriString* furi_string_cat_vprintf_test(FuriString* string, const char format[], ...) {
    va_list args;
    va_start(args, format);
    furi_string_cat_vprintf(string, format, args);
    va_end(args);
    return string;
}

MU_TEST(mu_test_furi_string_appends) {
    FuriString* tmp;
    FuriString* string = furi_string_alloc();

    // test furi_string_push_back
    furi_string_push_back(string, 't');
    furi_string_push_back(string, 'e');
    furi_string_push_back(string, 's');
    furi_string_push_back(string, 't');
    mu_assert_string_eq("test", furi_string_get_cstr(string));
    furi_string_push_back(string, '!');
    mu_assert_string_eq("test!", furi_string_get_cstr(string));

    // test furi_string_cat_str
    furi_string_cat_str(string, "test");
    mu_assert_string_eq("test!test", furi_string_get_cstr(string));

    // test furi_string_cat
    tmp = furi_string_alloc_set("more");
    furi_string_cat(string, tmp);
    furi_string_free(tmp);
    mu_assert_string_eq("test!testmore", furi_string_get_cstr(string));

    // test furi_string_cat_printf
    furi_string_cat_printf(string, "test %d %s %c 0x%02x", 1, "two", '3', 0x04);
    mu_assert_string_eq("test!testmoretest 1 two 3 0x04", furi_string_get_cstr(string));

    // test furi_string_cat_vprintf
    furi_string_cat_vprintf_test(string, "test %d %s %c 0x%02x", 4, "five", '6', 0x07);
    mu_assert_string_eq(
        "test!testmoretest 1 two 3 0x04test 4 five 6 0x07", furi_string_get_cstr(string));

    furi_string_free(string);
}

MU_TEST(mu_test_furi_string_compare) {
    FuriString* string_1 = furi_string_alloc_set("string_1");
    FuriString* string_2 = furi_string_alloc_set("string_2");

    // test furi_string_cmp
    mu_assert_int_eq(0, furi_string_cmp(string_1, string_1));
    mu_assert_int_eq(0, furi_string_cmp(string_2, string_2));
    mu_assert_int_eq(-1, furi_string_cmp(string_1, string_2));
    mu_assert_int_eq(1, furi_string_cmp(string_2, string_1));

    // test furi_string_cmp_str
    mu_assert_int_eq(0, furi_string_cmp_str(string_1, "string_1"));
    mu_assert_int_eq(0, furi_string_cmp_str(string_2, "string_2"));
    mu_assert_int_eq(-1, furi_string_cmp_str(string_1, "string_2"));
    mu_assert_int_eq(1, furi_string_cmp_str(string_2, "string_1"));

    // test furi_string_cmpi
    furi_string_set(string_1, "string");
    furi_string_set(string_2, "StrIng");
    mu_assert_int_eq(0, furi_string_cmpi(string_1, string_1));
    mu_assert_int_eq(0, furi_string_cmpi(string_2, string_2));
    mu_assert_int_eq(0, furi_string_cmpi(string_1, string_2));
    mu_assert_int_eq(0, furi_string_cmpi(string_2, string_1));
    furi_string_set(string_1, "string_1");
    furi_string_set(string_2, "StrIng_2");
    mu_assert_int_eq(32, furi_string_cmp(string_1, string_2));
    mu_assert_int_eq(-32, furi_string_cmp(string_2, string_1));
    mu_assert_int_eq(-1, furi_string_cmpi(string_1, string_2));
    mu_assert_int_eq(1, furi_string_cmpi(string_2, string_1));

    // test furi_string_cmpi_str
    furi_string_set(string_1, "string");
    mu_assert_int_eq(0, furi_string_cmp_str(string_1, "string"));
    mu_assert_int_eq(32, furi_string_cmp_str(string_1, "String"));
    mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STring"));
    mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRing"));
    mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRIng"));
    mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRINg"));
    mu_assert_int_eq(32, furi_string_cmp_str(string_1, "STRING"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "string"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "String"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STring"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRing"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRIng"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRINg"));
    mu_assert_int_eq(0, furi_string_cmpi_str(string_1, "STRING"));

    furi_string_free(string_1);
    furi_string_free(string_2);
}

MU_TEST(mu_test_furi_string_search) {
    //                                            012345678901234567
    FuriString* haystack = furi_string_alloc_set("test321test123test");
    FuriString* needle = furi_string_alloc_set("test");

    // test furi_string_search
    mu_assert_int_eq(0, furi_string_search(haystack, needle));
    mu_assert_int_eq(7, furi_string_search(haystack, needle, 1));
    mu_assert_int_eq(14, furi_string_search(haystack, needle, 8));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search(haystack, needle, 15));

    FuriString* tmp = furi_string_alloc_set("testnone");
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search(haystack, tmp));
    furi_string_free(tmp);

    // test furi_string_search_str
    mu_assert_int_eq(0, furi_string_search_str(haystack, "test"));
    mu_assert_int_eq(7, furi_string_search_str(haystack, "test", 1));
    mu_assert_int_eq(14, furi_string_search_str(haystack, "test", 8));
    mu_assert_int_eq(4, furi_string_search_str(haystack, "321"));
    mu_assert_int_eq(11, furi_string_search_str(haystack, "123"));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_str(haystack, "testnone"));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_str(haystack, "test", 15));

    // test furi_string_search_char
    mu_assert_int_eq(0, furi_string_search_char(haystack, 't'));
    mu_assert_int_eq(1, furi_string_search_char(haystack, 'e'));
    mu_assert_int_eq(2, furi_string_search_char(haystack, 's'));
    mu_assert_int_eq(3, furi_string_search_char(haystack, 't', 1));
    mu_assert_int_eq(7, furi_string_search_char(haystack, 't', 4));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_char(haystack, 'x'));

    // test furi_string_search_rchar
    mu_assert_int_eq(17, furi_string_search_rchar(haystack, 't'));
    mu_assert_int_eq(15, furi_string_search_rchar(haystack, 'e'));
    mu_assert_int_eq(16, furi_string_search_rchar(haystack, 's'));
    mu_assert_int_eq(13, furi_string_search_rchar(haystack, '3'));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_rchar(haystack, '3', 14));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_search_rchar(haystack, 'x'));

    furi_string_free(haystack);
    furi_string_free(needle);
}

MU_TEST(mu_test_furi_string_equality) {
    FuriString* string = furi_string_alloc_set("test");
    FuriString* string_eq = furi_string_alloc_set("test");
    FuriString* string_neq = furi_string_alloc_set("test2");

    // test furi_string_equal
    mu_check(furi_string_equal(string, string_eq));
    mu_check(!furi_string_equal(string, string_neq));

    // test furi_string_equal_str
    mu_check(furi_string_equal_str(string, "test"));
    mu_check(!furi_string_equal_str(string, "test2"));
    mu_check(furi_string_equal_str(string_neq, "test2"));
    mu_check(!furi_string_equal_str(string_neq, "test"));

    furi_string_free(string);
    furi_string_free(string_eq);
    furi_string_free(string_neq);
}

MU_TEST(mu_test_furi_string_replace) {
    FuriString* needle = furi_string_alloc_set("test");
    FuriString* replace = furi_string_alloc_set("replace");
    FuriString* string = furi_string_alloc_set("test123test");

    // test furi_string_replace_at
    furi_string_replace_at(string, 4, 3, "!biglongword!");
    mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string));

    // test furi_string_replace
    mu_assert_int_eq(17, furi_string_replace(string, needle, replace, 1));
    mu_assert_string_eq("test!biglongword!replace", furi_string_get_cstr(string));
    mu_assert_int_eq(0, furi_string_replace(string, needle, replace));
    mu_assert_string_eq("replace!biglongword!replace", furi_string_get_cstr(string));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_replace(string, needle, replace));
    mu_assert_string_eq("replace!biglongword!replace", furi_string_get_cstr(string));

    // test furi_string_replace_str
    mu_assert_int_eq(20, furi_string_replace_str(string, "replace", "test", 1));
    mu_assert_string_eq("replace!biglongword!test", furi_string_get_cstr(string));
    mu_assert_int_eq(0, furi_string_replace_str(string, "replace", "test"));
    mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string));
    mu_assert_int_eq(FURI_STRING_FAILURE, furi_string_replace_str(string, "replace", "test"));
    mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string));

    // test furi_string_replace_all
    furi_string_replace_all(string, needle, replace);
    mu_assert_string_eq("replace!biglongword!replace", furi_string_get_cstr(string));

    // test furi_string_replace_all_str
    furi_string_replace_all_str(string, "replace", "test");
    mu_assert_string_eq("test!biglongword!test", furi_string_get_cstr(string));

    furi_string_free(string);
    furi_string_free(needle);
    furi_string_free(replace);
}

MU_TEST(mu_test_furi_string_start_end) {
    FuriString* string = furi_string_alloc_set("start_end");
    FuriString* start = furi_string_alloc_set("start");
    FuriString* end = furi_string_alloc_set("end");

    // test furi_string_start_with
    mu_check(furi_string_start_with(string, start));
    mu_check(!furi_string_start_with(string, end));

    // test furi_string_start_with_str
    mu_check(furi_string_start_with_str(string, "start"));
    mu_check(!furi_string_start_with_str(string, "end"));

    // test furi_string_end_with
    mu_check(furi_string_end_with(string, end));
    mu_check(!furi_string_end_with(string, start));

    // test furi_string_end_with_str
    mu_check(furi_string_end_with_str(string, "end"));
    mu_check(!furi_string_end_with_str(string, "start"));

    furi_string_free(string);
    furi_string_free(start);
    furi_string_free(end);
}

MU_TEST(mu_test_furi_string_trim) {
    FuriString* string = furi_string_alloc_set("biglongstring");

    // test furi_string_left
    furi_string_left(string, 7);
    mu_assert_string_eq("biglong", furi_string_get_cstr(string));

    // test furi_string_right
    furi_string_right(string, 3);
    mu_assert_string_eq("long", furi_string_get_cstr(string));

    // test furi_string_mid
    furi_string_mid(string, 1, 2);
    mu_assert_string_eq("on", furi_string_get_cstr(string));

    // test furi_string_trim
    furi_string_set(string, "   \n\r\tbiglongstring \n\r\t  ");
    furi_string_trim(string);
    mu_assert_string_eq("biglongstring", furi_string_get_cstr(string));
    furi_string_set(string, "aaaabaaaabbaaabaaaabbtestaaaaaabbaaabaababaa");
    furi_string_trim(string, "ab");
    mu_assert_string_eq("test", furi_string_get_cstr(string));

    furi_string_free(string);
}

MU_TEST(mu_test_furi_string_utf8) {
    FuriString* utf8_string = furi_string_alloc_set("イルカ");

    // test furi_string_utf8_length
    mu_assert_int_eq(9, furi_string_size(utf8_string));
    mu_assert_int_eq(3, furi_string_utf8_length(utf8_string));

    // test furi_string_utf8_decode
    const uint8_t dolphin_emoji_array[4] = {0xF0, 0x9F, 0x90, 0xAC};
    FuriStringUTF8State state = FuriStringUTF8StateStarting;
    FuriStringUnicodeValue value = 0;
    furi_string_utf8_decode(dolphin_emoji_array[0], &state, &value);
    mu_assert_int_eq(FuriStringUTF8StateDecoding3, state);
    furi_string_utf8_decode(dolphin_emoji_array[1], &state, &value);
    mu_assert_int_eq(FuriStringUTF8StateDecoding2, state);
    furi_string_utf8_decode(dolphin_emoji_array[2], &state, &value);
    mu_assert_int_eq(FuriStringUTF8StateDecoding1, state);
    furi_string_utf8_decode(dolphin_emoji_array[3], &state, &value);
    mu_assert_int_eq(FuriStringUTF8StateStarting, state);
    mu_assert_int_eq(0x1F42C, value);

    // test furi_string_utf8_push
    furi_string_set(utf8_string, "");
    furi_string_utf8_push(utf8_string, value);
    mu_assert_string_eq("🐬", furi_string_get_cstr(utf8_string));

    furi_string_free(utf8_string);
}

MU_TEST_SUITE(test_suite) {
    MU_SUITE_CONFIGURE(&test_setup, &test_teardown);

    MU_RUN_TEST(mu_test_furi_string_alloc_free);
    MU_RUN_TEST(mu_test_furi_string_mem);
    MU_RUN_TEST(mu_test_furi_string_getters);
    MU_RUN_TEST(mu_test_furi_string_setters);
    MU_RUN_TEST(mu_test_furi_string_appends);
    MU_RUN_TEST(mu_test_furi_string_compare);
    MU_RUN_TEST(mu_test_furi_string_search);
    MU_RUN_TEST(mu_test_furi_string_equality);
    MU_RUN_TEST(mu_test_furi_string_replace);
    MU_RUN_TEST(mu_test_furi_string_start_end);
    MU_RUN_TEST(mu_test_furi_string_trim);
    MU_RUN_TEST(mu_test_furi_string_utf8);
}

int run_minunit_test_furi_string() {
    MU_RUN_SUITE(test_suite);

    return MU_EXIT_CODE;
}