/*
 * 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 "clar.h"

#include "fake_app_manager.h"
#include "fake_new_timer.h"
#include "fake_pbl_malloc.h"
#include "fake_pebble_tasks.h"
#include "fake_system_task.h"

#include "stubs_analytics.h"
#include "stubs_analytics_external.h"
#include "stubs_gettext.h"
#include "stubs_logging.h"
#include "stubs_logging.h"
#include "stubs_mutex.h"
#include "stubs_passert.h"
#include "stubs_persist.h"
#include "stubs_prompt.h"
#include "stubs_queue.h"
#include "stubs_resources.h"
#include "stubs_serial.h"
#include "stubs_syscall_internal.h"
#include "stubs_worker_manager.h"

#include "drivers/accel.h"
#include "services/common/event_service.h"
#include "util/math.h"
#include "util/size.h"

#include <stdio.h>

// helpers from accel manager
extern void test_accel_manager_get_subsample_info(
    AccelManagerState *state, uint16_t *num, uint16_t *den, uint16_t *samps_per_update);
extern void test_accel_manager_reset(void);

// stub
void event_service_init(PebbleEventType type,
                        EventServiceAddSubscriberCallback start_cb,
                        EventServiceRemoveSubscriberCallback stop_cb) {}
void sys_vibe_history_start_collecting(void) {}
void sys_vibe_history_stop_collecting(void) {}
int32_t sys_vibe_get_vibe_strength(void) {
  return 0;
}
void accel_set_shake_sensitivity_high(bool sensitivity_high) {}
QueueHandle_t pebble_task_get_to_queue(PebbleTask task) {
  return NULL;
}

// fake accel.h impl
static int s_sampling_interval_us = 1000000 / ACCEL_SAMPLING_25HZ;
static int s_num_samples = 0;

//! If true, ignore attempts to change the sampling interval
static bool s_force_sampling_interval;

uint32_t accel_set_sampling_interval(uint32_t interval_us) {
  if (!s_force_sampling_interval) {
    s_sampling_interval_us = interval_us;
  }
  return accel_get_sampling_interval();
}

uint32_t accel_get_sampling_interval(void) {
  return s_sampling_interval_us;
}

void accel_set_num_samples(uint32_t num_samples) {
  s_num_samples = num_samples;
}
int accel_peek(AccelDriverSample *data) {
  return 0;
}
void accel_enable_shake_detection(bool on) {
}
bool accel_get_shake_detection_enabled(void) {
  return false;
}
void accel_enable_double_tap_detection(bool on) {
}
bool accel_get_double_tap_detection_enabled(void) {
  return false;
}

bool accel_run_selftest(void) {
  return true;
}

bool gyro_run_selftest(void) {
  return true;
}

bool new_timer_add_work_callback_from_isr(NewTimerWorkCallback cb, void *data) {
  return false;
}
bool new_timer_add_work_callback(NewTimerWorkCallback cb, void *data) {
  return true;
}

// Unit Test Code

void test_accel_manager__initialize(void) {
  accel_manager_init();

  s_sampling_interval_us = 1000000 / ACCEL_SAMPLING_25HZ;
  s_num_samples = 0;
  s_force_sampling_interval = false;
}


void test_accel_manager__cleanup(void) {
  test_accel_manager_reset();
}

static void prv_noop_sample_handler(void *context) {

}

static void prv_validate_sample_rates(int *arr, int num_samples) {
  for (int i = 0; i < num_samples; i++) {
    // force a compiler error if user has not added all possible sample rates to array
    switch ((AccelSamplingRate)arr[i]) {
      case ACCEL_SAMPLING_10HZ:
      case ACCEL_SAMPLING_25HZ:
      case ACCEL_SAMPLING_50HZ:
      case ACCEL_SAMPLING_100HZ:
        break;
      default:
        cl_assert(0);
    }
  }
}

static void prv_run_accel_test(int *sample_arr, int num_items) {
  PebbleTask tasks[] = { PebbleTask_KernelMain, PebbleTask_Worker, PebbleTask_App };
  AccelManagerState* sessions[3];

  if (num_items > 3) {
    return; // we only support 3 simultaneous subscribers
  }

  int fastest_rate = 0;
  AccelRawData fake_buf[1];
  for (int i = 0; i < num_items; i++) {
    if (fastest_rate < sample_arr[i]) {
      fastest_rate = sample_arr[i];
    }

    sessions[i] = sys_accel_manager_data_subscribe(
        sample_arr[i], prv_noop_sample_handler, NULL, tasks[i]);

    // buffer size of 1
    sys_accel_manager_set_sample_buffer(sessions[i], fake_buf, 1);
  }

  //  make sure all sampling rates are what they should be
  for (int i = 0; i < num_items; i++) {
    stub_pebble_tasks_set_current(tasks[i]);

    uint16_t num, den, samps_per_update;
    test_accel_manager_get_subsample_info(sessions[i], &num, &den, &samps_per_update);

    if ((fastest_rate % sample_arr[i]) == 0) {
      // the current sample rate is a multiple of the rate we are running at
      cl_assert_equal_i(num, 1);
      cl_assert_equal_i(den, fastest_rate / sample_arr[i]);
      cl_assert_equal_i(samps_per_update, 1);
    } else {
      // the sample rate is not an even multiple of our fastest rate
      uint32_t gcd_of_rates = gcd(fastest_rate, sample_arr[i]);
      cl_assert_equal_i(num, sample_arr[i] / gcd_of_rates);
      cl_assert_equal_i(den, fastest_rate / gcd_of_rates);
      cl_assert_equal_i(samps_per_update, 1);
    }
  }

  cl_assert_equal_i(1000000 / s_sampling_interval_us, fastest_rate);
  cl_assert_equal_i(s_num_samples, 1);

  for (int i = 0; i < num_items; i++) {
    sys_accel_manager_data_unsubscribe(sessions[i]);
    stub_pebble_tasks_set_current(tasks[i]);
  }
}

// enumerate through all possible sampling rate combinations and confirm
// that the correct frequency is selected
void test_accel_manager__subscription_sampling_rates(void) {
  int sample_rates[] = { ACCEL_SAMPLING_10HZ, ACCEL_SAMPLING_25HZ,
                         ACCEL_SAMPLING_50HZ, ACCEL_SAMPLING_100HZ};
  prv_validate_sample_rates(sample_rates, ARRAY_LENGTH(sample_rates));

  int poss_rates = ARRAY_LENGTH(sample_rates);
  int max_permutations = 0x1 << poss_rates;

  for (int mask = 0; mask < max_permutations; mask++) {
    int count = __builtin_popcount(mask);
    if (count == 0) {
      continue; // we don't care about the empty set
    }

    int test_rates[count];
    int idx = 0;
    for (int j = 0; j < poss_rates; j++) {
      if ((mask & (0x1 << j)) != 0) {
        test_rates[idx] = sample_rates[j];
        idx++;
      }
    }

    printf("Testing: ");
    for (int i = 0; i < count; i++) {
      printf("%d ", sample_rates[i]);
    }
    printf("\n");

    prv_run_accel_test(test_rates, count);
  }
}

void test_accel_manager__jitterfree(void) {
  // Force the fake accel to only support the 125hz sample rate.
  s_force_sampling_interval = true;
  s_sampling_interval_us = (1000000000 / 125000);

  AccelRawData fake_buf[1];

  AccelManagerState *state = sys_accel_manager_data_subscribe(
      ACCEL_SAMPLING_25HZ, prv_noop_sample_handler, NULL, PebbleTask_KernelMain);
  uint32_t resulting_mhz = accel_manager_set_jitterfree_sampling_rate(state, 12500);
  sys_accel_manager_set_sample_buffer(state , fake_buf, ARRAY_LENGTH(fake_buf));

  cl_assert_equal_i(resulting_mhz, 12500);

  uint16_t num, den, samples_per_update;
  test_accel_manager_get_subsample_info(state, &num, &den, &samples_per_update);

  cl_assert_equal_i(num, 1);
  cl_assert_equal_i(den, 10);
  cl_assert_equal_i(samples_per_update, ARRAY_LENGTH(fake_buf));
}


void test_accel_manager__batched_samples(void) {
  AccelRawData fake_buf[30];

  stub_pebble_tasks_set_current(PebbleTask_KernelMain);
  AccelManagerState *main_session = sys_accel_manager_data_subscribe(
      ACCEL_SAMPLING_10HZ, prv_noop_sample_handler, NULL, PebbleTask_KernelMain);
  sys_accel_manager_set_sample_buffer(main_session, fake_buf, 11);

  stub_pebble_tasks_set_current(PebbleTask_Worker);
  AccelManagerState *worker_session = sys_accel_manager_data_subscribe(
      ACCEL_SAMPLING_25HZ, prv_noop_sample_handler, NULL, PebbleTask_KernelMain);
  sys_accel_manager_set_sample_buffer(worker_session, fake_buf, 22);

  cl_assert_equal_i(s_num_samples, 22);

  stub_pebble_tasks_set_current(PebbleTask_KernelMain);
  sys_accel_manager_set_sample_buffer(main_session, fake_buf, 3);
  cl_assert_equal_i(s_num_samples, 7); /* 300ms / (1000ms / 25 samps) */
}