#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "applib/accel_service.h"
#include "util/time/time.h"


// ---------------------------------------------------------------------------------------------
// Equates
// number of samples per second
#define KALG_SAMPLE_HZ 25

// Number of grams per kilogram
#define KALG_GRAMS_PER_KG 1000

typedef struct KAlgState KAlgState;

// This value in the encoded_vmc field of a KalgSleepMinute structure indicates that the
// watch was not worn
#define KALG_ENCODED_VMC_NOT_WORN 0

// The minimum value if the watch was worn
#define KALG_ENCODED_VMC_MIN_WORN_VALUE 1

// The maximum amount of time it takes for the sleep algorithm to figure out that the user
// woke up. This is used by activity_algorithm_kraepelin.c at compile time so it must be
// hard-coded. The correct value is checked in kalg_init() using a PBL_ASSERT since it can't
// be checked at compile time.
// should be: KALG_SLEEP_PARAMS.max_wake_minutes_early + KALG_SLEEP_HALF_WIDTH + 1
#define KALG_MAX_UNCERTAIN_SLEEP_M 19

// Activity types, used in KAlgSleepSessionCallback callback
typedef enum {
  // ActivityType_Sleep encapsulates an entire sleep session from sleep entry to wake, and
  // contains both light and deep sleep periods. An ActivityType_RestfulSleep session identifies
  // a restful period and its start and end times will always be inside of a ActivityType_Sleep
  // session.
  KAlgActivityType_Sleep,

  // A restful period, these will always be inside of a ActivityType_Sleep session
  KAlgActivityType_RestfulSleep,

  // A "sigificant" length walk
  KAlgActivityType_Walk,

  // A run
  KAlgActivityType_Run,

  // Leave at end
  KAlgActivityTypeCount,
} KAlgActivityType;

// Sleep stats, returned by kalg_get_sleep_stats
typedef struct {
  time_t sleep_start_utc;     // start time of a recent sleep session. 0 if no session recently
                              // detected, where "recent" means within the last
                              // minimum_sleep_session_length minutes (currently 60).
  uint16_t sleep_len_m;       // how many minutes of that sleep are *certain*, 0 if none.
  time_t uncertain_start_utc; // start time of the uncertain area of the sleep session, which
                              // always continues until the present time, 0 if none.
} KAlgOngoingSleepStats;

// Callback called by kalg_activities_update to register activity sessions.
// @param[in] context the context passed to kalg_activities_update()
// @param[in] activity_type the type of activity
// @param[in] start_utc start time of the activity
// @param[in] len_sec length of the activity
// @param[in] ongoing true if the activity is still ongoing, false if it ended
// @param[in] delete if true, delete this session that was previously added
// @param[in] steps the number of steps taken in this activity
// @param[in] resting_calories the number of resting calories burned
// @param[in] active_calories the number of active calories burned
// @param[in] distance_mm the distance covered, in millimeters
typedef void (*KAlgActivitySessionCallback)(void *context, KAlgActivityType activity_type,
                                            time_t start_utc, uint32_t len_sec, bool ongoing,
                                            bool delete, uint32_t steps, uint32_t resting_calories,
                                            uint32_t active_calories, uint32_t distance_mm);

// Callback called by kalg_analyze_samples and kalg_compute_activities to record
// statistics. This is used during algorithm development, not during normal runtime. The algorithm
// passes a list of statistic names and their values to this callback so that they can be
// collected and summarized
// @param[in] num_stats the number of elements in the names and stats arrays
// @param[in] names list of statistic names
// @param[in] stats the value for each statistic
typedef void (*KAlgStatsCallback)(uint32_t num_stats, const char **names, int32_t *stats);

// Return the amount of space needed for the state
uint32_t kalg_state_size(void);

// Init the state, return true on success
// @param[in] stats_cb if not NULL, this callback will be called while analyzing samples with
//  statistics that are computed.
bool kalg_init(KAlgState *state, KAlgStatsCallback stats_cb);

// Analyze a set of accel samples
// @param[in] state the state structure passed into kalg_init
// @param[in] samples array of accel samples
// @param[in] num_samples number of samples in the samples array
// @param[out] consumed_samples The number of samples that were just consumed to compute steps
//             For many algorithms, this will often be 0 because the algorithm will wait until
//             it has stored up a larger batch before it runs the step algorithm on the samples.
// @return number of steps counted
uint32_t kalg_analyze_samples(KAlgState *state, AccelRawData *samples, uint32_t num_samples,
                              uint32_t *consumed_samples);

// Return the last minute's stats and reset them for the next minute. The minute stats are logged
// and also used for computing sleep
// @param[in] state the state structure passed into kalg_init
// @param[out] vmc the vmc (Vector Magnitude Counts) value is returned here
// @param[out] orientation the orientation value is returned here
// @param[out] still true if no movement above noise threshold was detected
void kalg_minute_stats(KAlgState *state, uint16_t *vmc, uint8_t *orientation, bool *still);

// Used by unit tests - run the partial epoch that hasn't been processed yet by
// kalg_analyze_samples()
// @param[in] state the state structure passed into kalg_init
// @return number of steps counted
uint32_t kalg_analyze_finish_epoch(KAlgState *state);

// Feed new minute data into the activity detection state machine. This logic looks for non-sleep
// activities, like walks, runs, etc.
// @param[in] state the state structure passed into kalg_init
// @param[in] utc_now current timestamp in UTC
// @param[in] steps number of steps taken in the last minute
// @param[in] vmc VMC for the last minute
// @param[in] orientation average orientation for the last minute
// @param[in] plugged_in true if watch is plugged into charger
// @param[in] resting_calories number of resting calories burned in the last minute
// @param[in] active_calories number of active calories burned in the last minute
// @param[in] distance_mm distance covered in millimeters in the last minute
// @param[in] sessions_cb this callback will be called by kalg_compute_activities() for every
//            session that it finds.
// @param[in] context passed to the sessions_cb
void kalg_activities_update(KAlgState *state, time_t utc_now, uint16_t steps, uint16_t vmc,
                            uint8_t orientation, bool plugged_in, uint32_t resting_calories,
                            uint32_t active_calories, uint32_t distance_mm, bool shutting_down,
                            KAlgActivitySessionCallback sessions_cb, void *context);

// Return the timestamp of the last minute that was processed for the given activity type
// @param[in] state the state structure passed into kalg_init
// @param[in] activity which type of activity
time_t kalg_activity_last_processed_time(KAlgState *state, KAlgActivityType activity);

// Get sleep stats
// @param[out] stats this structure is filled in with the sleep stats
void kalg_get_sleep_stats(KAlgState *state, KAlgOngoingSleepStats *stats);

//! Tells the algorithm whether or not it should automatically track activities
//! @param enable true to start tracking, false to stop tracking
void kalg_enable_activity_tracking(KAlgState *kalg_state, bool enable);
