/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "comm/ble/kernel_le_client/ams/ams_util.h"

#include "clar.h"

// Stubs
///////////////////////////////////////////////////////////

#include "stubs_logging.h"

// Helpers
///////////////////////////////////////////////////////////

static char s_results[10][32];
static uint32_t s_results_lengths[10];
static uint8_t s_results_count;
static uint32_t s_max_results_count;

static bool prv_result_callback(const char *value, uint32_t value_length,
                                uint32_t index, void *context) {
  strcpy(s_results[s_results_count], value);
  s_results_lengths[s_results_count] = value_length;
  cl_assert_equal_i(index, s_results_count);
  ++s_results_count;

  return (s_max_results_count > s_results_count);
}

// Tests
///////////////////////////////////////////////////////////

void test_ams_util__initialize(void) {
  memset(s_results, 0, sizeof(s_results));
  s_results_count = 0;
  s_max_results_count = ~0;
}

void test_ams_util__cleanup(void) {
}

// ams_util_float_string_parse() tests
///////////////////////////////////////////////////////////

#define assert_float_parse(in, mul, succeeds, expected_result) \
  { \
    const char *input = (const char *)in; \
    const uint32_t input_length = input ? sizeof(in) : 0; \
    const uint32_t multiplier = mul; \
    int32_t result = 0; \
    cl_assert_equal_b(succeeds, \
          ams_util_float_string_parse((const char *)in, input_length, multiplier, &result)); \
    if (succeeds) { \
      cl_assert_equal_i(result, expected_result); \
    } \
  }

void test_ams_util__float_string_parse_negative_number(void) {
  // "-1" * 3
  assert_float_parse("-1", 3, true, -1 * 3);
}

void test_ams_util__float_string_parse_only_minus_sign(void) {
  // "-" * 3
  assert_float_parse("-", 3, false, 0);
}

void test_ams_util__float_string_parse_negative_number_nothing_before_separator(void) {
  // "-.1" * 30
  assert_float_parse("-.1", 30, true, -3);
}

void test_ams_util__float_string_parse_multiple_minusses(void) {
  // "--.1" * 30
  assert_float_parse("--.1", 30, false, 0);
}

void test_ams_util__float_string_parse_null(void) {
  // NULL * 3
  assert_float_parse(NULL, 3, false, 0);
}

void test_ams_util__float_string_parse_not_zero_terminated(void) {
  uint8_t buffer[] = {'1'};
  assert_float_parse(buffer, 3, true, 3);
}

void test_ams_util__float_string_parse_null_in_the_middle(void) {
  uint8_t buffer[] = {'1', 0, '2'};
  assert_float_parse(buffer, 3, true, 3);
}

void test_ams_util__float_string_parse_empty_string(void) {
  // "" * 3
  assert_float_parse("", 3, false, 0);
}

void test_ams_util__float_string_parse_not_a_number(void) {
  // "hello" * 3
  assert_float_parse("hello", 3, false, 0);
  // " " * 3
  assert_float_parse(" ", 3, false, 0);
}

void test_ams_util__float_string_parse_no_fraction(void) {
  // "42" * 3
  assert_float_parse("42", 3, true, 42 * 3);
}

void test_ams_util__float_string_parse_separator_but_no_fraction(void) {
  // "21." * 3
  assert_float_parse("21.", 3, true, 21 * 3);
}

void test_ams_util__float_string_parse_comma_decimal_separator(void) {
  // "1.23456" * 3 is approx 3.7036800000000003, round up to 4
  assert_float_parse("1,23456", 3, true, 4);
}

void test_ams_util__float_string_parse_period_decimal_separator(void) {
  // "1.23456" * 3 is approx 3.7036800000000003, round up to 4
  assert_float_parse("1.23456", 3, true, 4);
}

void test_ams_util__float_string_parse_round_down(void) {
  // "0.74995" * 2 is approx 1.4999, round down to 1
  assert_float_parse("0.74995", 2, true, 1);
}

void test_ams_util__float_string_parse_multiple_separators(void) {
  // "1.2.2" * 3
  assert_float_parse("1.2.2", 2, false, 0);
}

void test_ams_util__float_string_positive_multiplier(void) {
  // "1.654321" * 33 is approx 54.592593, round up to 55
  assert_float_parse("1.654321", 33, true, 55);
}

void test_ams_util__float_string_negative_multiplier(void) {
  // "1.987622" * -33 is approx -65.591526, round to -66
  assert_float_parse("1.987622", -33, true, -66);
}

void test_ams_util__float_string_parse_overflow_positive(void) {
  // fails:
  // "2147483648" * 1
  assert_float_parse("2147483648", 1, false, 0);

  // succeeds:
  // "2147483647" * 1
  assert_float_parse("2147483647", 1, true, 2147483647);
}

void test_ams_util__float_string_parse_overflow_negative(void) {
  // fails:
  // "-2147483649" * 1
  assert_float_parse("-2147483649", 1, false, 0);

  // succeeds:
  // "-2147483648" * 1
  assert_float_parse("-2147483648", 1, true, -2147483648);
  assert_float_parse("2147483648", -1, true, -2147483648);
}

// ams_util_csv_parse() tests
///////////////////////////////////////////////////////////

#define assert_result(idx, val, len); \
  cl_assert_equal_i(len, s_results_lengths[idx]); \
  cl_assert_equal_i(memcmp(val, s_results[idx], len), 0);

void test_ams_util__csv_empty_string(void) {
  const char empty_string[] = "";
  const uint8_t count = ams_util_csv_parse(empty_string, sizeof(empty_string), NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 1);
  cl_assert_equal_i(count, 1);
  cl_assert_equal_s(empty_string, s_results[0]);
}

void test_ams_util__csv_empty_values(void) {
  const char one_value[] = ",";
  const uint8_t count = ams_util_csv_parse(one_value, sizeof(one_value), NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 2);
  cl_assert_equal_i(count, 2);
  assert_result(0, "", 0);
  assert_result(1, "", 0);
}

void test_ams_util__csv_null(void) {
  const uint8_t count = ams_util_csv_parse(NULL, 0, NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 0);
  cl_assert_equal_i(count, 0);
}

void test_ams_util__csv_one_value(void) {
  const char one_value[] = "A";
  const uint8_t count = ams_util_csv_parse(one_value, sizeof(one_value), NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 1);
  cl_assert_equal_i(count, 1);
  cl_assert_equal_s(one_value, s_results[0]);
}

void test_ams_util__csv_multiple_values(void) {
  const char multi_values[] = "A,B,C";
  const uint8_t count = ams_util_csv_parse(multi_values, sizeof(multi_values), NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 3);
  cl_assert_equal_i(count, 3);
  assert_result(0, "A", 1);
  assert_result(1, "B", 1);
  assert_result(2, "C", 1);
}

void test_ams_util__csv_stop_after_one_value(void) {
  const char multi_values[] = "A,B,C";
  s_max_results_count = 1;
  const uint8_t count = ams_util_csv_parse(multi_values, sizeof(multi_values), NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 1);
  cl_assert_equal_i(count, 1);
  assert_result(0, "A", 1);
}

void test_ams_util__csv_null_in_the_middle(void) {
  const char null_middle_value[] = "A\x00 BCD,1234";
  cl_assert(sizeof(null_middle_value) > 2);
  const uint8_t count = ams_util_csv_parse(null_middle_value, sizeof(null_middle_value), NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 1);
  cl_assert_equal_i(count, 1);
  assert_result(0, "A", 1);
}

void test_ams_util__csv_buffer_not_zero_terminated(void) {
  const char one_value[] = "ABCDEF";
  cl_assert(sizeof(one_value) > 2);
  const uint8_t count = ams_util_csv_parse(one_value,
                                           sizeof(one_value) - 1 /* omit zero teminator */, NULL,
                                           prv_result_callback);
  cl_assert_equal_i(s_results_count, 1);
  cl_assert_equal_i(count, 1);
  assert_result(0, "ABCDEF", 6);
}