/*
 * 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 "services/normal/activity/activity.h"
#include "services/normal/activity/activity_private.h"
#include "services/normal/activity/activity_calculators.h"

#include "util/units.h"

#include <stdint.h>

// Fakes
static uint8_t s_age_years;
uint8_t activity_prefs_get_age_years(void) {
  return s_age_years;
}

static ActivityGender s_gender;
ActivityGender activity_prefs_get_gender(void) {
  return s_gender;
}

static uint16_t s_weight_dag;
uint16_t activity_prefs_get_weight_dag(void) {
  return s_weight_dag;
}

static uint16_t s_height_mm;
uint16_t activity_prefs_get_height_mm(void) {
  return s_height_mm;
}

typedef enum {
  Human_TallMale,
  Human_ShortMale,
  Human_TallFemale,
  Human_ShortFemale,
  Human_Count,
} Human;

typedef struct HumanPrefs {
  uint8_t age_years;
  ActivityGender gender;
  uint16_t weight_dag;
  uint16_t height_mm;
} HumanPrefs;

#define ACTIVITY_DEFAULT_HEIGHT_MM                1620    // 5'3.8"
// dag - decagram (10 g)
#define ACTIVITY_DEFAULT_WEIGHT_DAG               7539    // 166.2 lbs
#define ACTIVITY_DEFAULT_GENDER                   ActivityGenderFemale
#define ACTIVITY_DEFAULT_AGE_YEARS                30

static void prv_set_user(Human type) {
  const HumanPrefs types[Human_Count] = {
    [Human_TallMale] =    {30, ActivityGenderMale,   7539, 1900},
    [Human_ShortMale] =   {30, ActivityGenderMale,   4536, 1620},
    [Human_TallFemale] =  {30, ActivityGenderFemale, 7539, 1900},
    [Human_ShortFemale] = {30, ActivityGenderFemale, 4536, 1620},
  };

  s_age_years = types[type].age_years;
  s_gender = types[type].gender;
  s_weight_dag = types[type].weight_dag;
  s_height_mm = types[type].height_mm;
}

#define MM_PER_METER 1000
#define M_PER_KM 1000

// =============================================================================================
// Start of unit tests
void test_activity_calculators__initialize(void) {
}

// ---------------------------------------------------------------------------------------
void test_activity_calculators__cleanup(void) {
}

// ---------------------------------------------------------------------------------------
void test_activity_calculators__distance(void) {
  uint32_t steps;
  uint32_t time_ms;

  prv_set_user(Human_TallMale);

  const int walking_cadence_spm = 100;

  // Do an normal walk for 12 mins. An average person should cover ~1km
  const int walk_time = 12;
  steps = walking_cadence_spm * walk_time;
  time_ms = walk_time * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int easy_walk_distance_m = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_within(easy_walk_distance_m, 900, 1100);

  // Walk for 12 mins again, but this time faster. More distance should be covered
  steps = (walking_cadence_spm * 1.2) * walk_time;
  time_ms = walk_time * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int fast_walk_distance_m = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_gt(fast_walk_distance_m, easy_walk_distance_m);

  // Walk for a long time. People can walk at roughly 5km/h, so we should be close to 50km
  const int long_walk_time = 10 * MINUTES_PER_HOUR;
  steps = walking_cadence_spm * long_walk_time;
  time_ms = long_walk_time * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int long_walk_distance_m = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_within(long_walk_distance_m, 48000, 52000);


  // A typical cadence is roughly 165 steps per minute
  const int running_cadence_spm = 165;

  // Running for 25 minutes should come out to roughly 5km
  const int run_time = 25;
  steps = running_cadence_spm * run_time;
  time_ms = run_time * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int normal_run_distance_m = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_within(normal_run_distance_m, 4500, 5500);

  // Running for 25 minutes again, but this time faster
  steps = (running_cadence_spm * 1.15) * run_time;
  time_ms = run_time * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int fast_run_distance_m = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_within(fast_run_distance_m, 6500, 7000);
  cl_assert_gt(fast_run_distance_m, normal_run_distance_m);

  // Run for 3.5 hours. This is a reasonable marathon time
  const int long_run_time = 3 * MINUTES_PER_HOUR + 30;
  steps = running_cadence_spm * long_run_time;
  time_ms = long_run_time * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int long_run_distance_m = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_within(long_run_distance_m, 40000, 44000);

  // Now make a shorter guy run for the same time.
  // He should be in the same ballpark but cover less distance
  prv_set_user(Human_ShortMale);
  int short_guy_distance = activity_private_compute_distance_mm(steps, time_ms) / MM_PER_METER;
  cl_assert_within(short_guy_distance, 36000, 44000);
  cl_assert_gt(long_run_distance_m, short_guy_distance);

  // And finally throw in a specific value so that anyone who touches the function will have to
  // check up on the unit tests
  cl_assert_equal_i(short_guy_distance, 36845);
}

// ---------------------------------------------------------------------------------------
void test_activity_calculators__active_calories(void) {
  uint32_t distance_mm;
  uint32_t time_ms;

  prv_set_user(Human_ShortMale);

  // Walk 1km in 12 minutes
  distance_mm = 1 * M_PER_KM * MM_PER_METER;
  time_ms = 12 * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int walk_calories = activity_private_compute_active_calories(distance_mm, time_ms) / ACTIVITY_CALORIES_PER_KCAL;
  cl_assert_within(walk_calories, 20, 25); // This seems a little low, but not un-reasonable

  // Run 1km in 5 minutes. This should burn more calories than walking
  distance_mm = 1 * M_PER_KM * MM_PER_METER;
  time_ms = 5 * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int run_calories = activity_private_compute_active_calories(distance_mm, time_ms) / ACTIVITY_CALORIES_PER_KCAL;
  cl_assert_within(run_calories, 40, 60); // This also seems a little low, but not un-reasonable
  cl_assert_gt(run_calories, walk_calories);

  // Run 5km in 25 minutes
  distance_mm = 5 * M_PER_KM * MM_PER_METER;
  time_ms = 25 * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int five_k_calories = activity_private_compute_active_calories(distance_mm, time_ms) / ACTIVITY_CALORIES_PER_KCAL;
  cl_assert_within(five_k_calories, 220, 250);
  cl_assert_gt(five_k_calories, run_calories);

  // PG: I went for the following run last night and my garmin watch said I burned 550 calories
  prv_set_user(Human_TallMale);
  distance_mm = 7 * M_PER_KM * MM_PER_METER;
  time_ms = 30 * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int quick_run_calories = activity_private_compute_active_calories(distance_mm, time_ms) / ACTIVITY_CALORIES_PER_KCAL;
  cl_assert_within(quick_run_calories, 520, 580);

  // Run a marathon
  distance_mm = 42 * M_PER_KM * MM_PER_METER;
  time_ms = 3 * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MS_PER_SECOND;
  int long_run_calories = activity_private_compute_active_calories(distance_mm, time_ms) / ACTIVITY_CALORIES_PER_KCAL;
  cl_assert_within(long_run_calories, 3000, 3200);

  // And finally throw in a specific value so that anyone who touches the function will have to
  // check up on the unit tests
  cl_assert_equal_i(long_run_calories, 3172);
}

// ---------------------------------------------------------------------------------------
void test_activity_calculators__inactive_calories(void) {
  // People burn roughly 2000 (women) - 2400 (men) a day. This number includes active calories
  // so I would expect the values we get to be less than that. I don't know enough to make
  // better real world analogies though...

  uint32_t long_time_m = 24 * MINUTES_PER_HOUR;
  uint32_t short_time_m = 5;

  prv_set_user(Human_ShortMale);

  cl_assert_equal_i(1321, activity_private_compute_resting_calories(long_time_m) / ACTIVITY_CALORIES_PER_KCAL);
  cl_assert_equal_i(4, activity_private_compute_resting_calories(short_time_m) / ACTIVITY_CALORIES_PER_KCAL);

  prv_set_user(Human_TallMale);

  cl_assert_equal_i(1796, activity_private_compute_resting_calories(long_time_m)/ ACTIVITY_CALORIES_PER_KCAL);
  cl_assert_equal_i(6, activity_private_compute_resting_calories(short_time_m) / ACTIVITY_CALORIES_PER_KCAL);

  prv_set_user(Human_ShortFemale);

  cl_assert_equal_i(1155, activity_private_compute_resting_calories(long_time_m) / ACTIVITY_CALORIES_PER_KCAL);
  cl_assert_equal_i(4, activity_private_compute_resting_calories(short_time_m) / ACTIVITY_CALORIES_PER_KCAL);

  prv_set_user(Human_TallFemale);

  cl_assert_equal_i(1630, activity_private_compute_resting_calories(long_time_m) / ACTIVITY_CALORIES_PER_KCAL);
  cl_assert_equal_i(5, activity_private_compute_resting_calories(short_time_m) / ACTIVITY_CALORIES_PER_KCAL);

}
