// Copyright (c) Acconeer AB, 2020-2021
// All rights reserved
// This file is subject to the terms and conditions defined in the file
// 'LICENSES/license_acconeer.txt', (BSD 3-Clause License) which is part
// of this source code package.

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "sys_app.h"
#include "acc_definitions_common.h"
#include "acc_detector_distance.h"
#include "acc_hal_definitions.h"
#include "acc_hal_integration.h"
#include "acc_rss.h"
#include "acc_version.h"


// Default values for this reference application
// Note that if a range longer than 7 m is used, an update of 'mur' is required.
// See API documentation for more information of respective parameter
#define DEFAULT_SENSOR 1

#define DEFAULT_CLOSE_RANGE_MUR                      ACC_SERVICE_MUR_6
#define DEFAULT_CLOSE_RANGE_START                    -0.11f
#define DEFAULT_CLOSE_RANGE_LENGTH                   0.23f
#define DEFAULT_CLOSE_RANGE_GAIN                     0.3182f
#define DEFAULT_CLOSE_MAXIMIZE_SIGNAL_ATTENUATION    true
#define DEFAULT_CLOSE_RANGE_SERVICE_PROFILE          ACC_SERVICE_PROFILE_1
#define DEFAULT_CLOSE_RANGE_DOWNSAMPLIG_FACTOR       1
#define DEFAULT_CLOSE_RANGE_SWEEP_AVERAGING          30
#define DEFAULT_CLOSE_RANGE_THRESHOLD_TYPE           ACC_DETECTOR_DISTANCE_THRESHOLD_TYPE_RECORDED
#define DEFAULT_CLOSE_RANGE_RECORD_BACKGROUND_SWEEPS 30
#define DEFAULT_CLOSE_RANGE_THRESHOLD_SENSITIVITY    0.2f

#define DEFAULT_MID_RANGE_MUR                      ACC_SERVICE_MUR_6
#define DEFAULT_MID_RANGE_START                    0.1f
#define DEFAULT_MID_RANGE_LENGTH                   0.37f
#define DEFAULT_MID_RANGE_GAIN                     0.5f
#define DEFAULT_MID_MAXIMIZE_SIGNAL_ATTENUATION    false
#define DEFAULT_MID_RANGE_SERVICE_PROFILE          ACC_SERVICE_PROFILE_1
#define DEFAULT_MID_RANGE_DOWNSAMPLING_FACTOR      1
#define DEFAULT_MID_RANGE_SWEEP_AVERAGING          30
#define DEFAULT_MID_RANGE_THRESHOLD_TYPE           ACC_DETECTOR_DISTANCE_THRESHOLD_TYPE_RECORDED
#define DEFAULT_MID_RANGE_RECORD_BACKGROUND_SWEEPS 30
#define DEFAULT_MID_RANGE_THRESHOLD_SENSITIVITY    0.2f

#define DEFAULT_FAR_RANGE_MUR                   ACC_SERVICE_MUR_6
#define DEFAULT_FAR_RANGE_START                 0.19f
#define DEFAULT_FAR_RANGE_LENGTH                3.3f			//1.3f
#define DEFAULT_FAR_RANGE_GAIN                  0.8182f			//0.8182f
#define DEFAULT_FAR_MAXIMIZE_SIGNAL_ATTENUATION false
#define DEFAULT_FAR_RANGE_SERVICE_PROFILE       ACC_SERVICE_PROFILE_2
#define DEFAULT_FAR_RANGE_DOWNSAMPLING_FACTOR   4
#define DEFAULT_FAR_RANGE_SWEEP_AVERAGING       10
#define DEFAULT_FAR_RANGE_THRESHOLD_TYPE        ACC_DETECTOR_DISTANCE_THRESHOLD_TYPE_CFAR
#define DEFAULT_FAR_RANGE_THRESHOLD_SENSITIVITY 0.4f
#define DEFAULT_FAR_RANGE_CFAR_THRESHOLD_GUARD  0.12f
#define DEFAULT_FAR_RANGE_CFAR_THRESHOLD_WINDOW 0.03f

// Gain can be configured in 22 steps between 0.0 and 1.0
#define GAIN_STEP             (1.0f / 22.0f)
#define MAX_BACKGROUND_LENGTH 1200


static uint16_t close_background[MAX_BACKGROUND_LENGTH];
static uint16_t close_background_length;
static float    close_range_gain = DEFAULT_CLOSE_RANGE_GAIN;

static uint16_t mid_background[MAX_BACKGROUND_LENGTH];
static uint16_t mid_background_length;
static float    mid_range_gain = DEFAULT_MID_RANGE_GAIN;


/**
 * Calibrate the sensor
 *
 * @return True, if sensor calibration was successful
 */
static bool sensor_calibration(void);


/**
 * Configure distance detector to measure in the close range sector
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @return True, if configuration was successful
 */
static bool configure_close_range(acc_detector_distance_handle_t        *distance_handle,
                                  acc_detector_distance_configuration_t distance_configuration);


/**
 * Configure distance detector to measure in the mid range sector
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @return True, if configuration was successful
 */
static bool configure_mid_range(acc_detector_distance_handle_t        *distance_handle,
                                acc_detector_distance_configuration_t distance_configuration);


/**
 * Configure distance detector to measure in the far range sector
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @return True, if configuration was successful
 */
static bool configure_far_range(acc_detector_distance_handle_t        *distance_handle,
                                acc_detector_distance_configuration_t distance_configuration);


/**
 * Record the background for specific configuration
 *
 * This function will set the requested gain, but the gain will be lowered in case of data saturation.
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @param range_gain The gain actually used
 * @param background Array to store background in
 * @param background_length Length of background array
 * @return True, if recording was successful
 */
static bool record_background(acc_detector_distance_handle_t *distance_handle,
                              acc_detector_distance_configuration_t distance_configuration,
                              float *range_gain, uint16_t *background, uint16_t background_length);


/**
 * Record background threshold for close and mid range sector
 *
 * Make sure no object is within these two sectors, i.e. no objects closer
 * to the sensor than DEFAULT_MID_RANGE_START + DEFAULT_MID_RANGE_LENGTH
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @return True, if recording was successful
 */
static bool record_backgrounds(acc_detector_distance_handle_t        *distance_handle,
                               acc_detector_distance_configuration_t distance_configuration);


/**
 * Perform one measurement
 *
 * This function will set the requested gain, but the gain will be lowered in case of data saturation.
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @param result Distance Detector result
 * @param result_info Distance Detector result info
 */
static bool measurement(acc_detector_distance_handle_t        *distance_handle,
                        acc_detector_distance_configuration_t distance_configuration,
                        acc_detector_distance_result_t        *result,
                        acc_detector_distance_result_info_t   *result_info);


/**
 * Measure in the close range sector
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @param distance_detected True, if a distance was detected
 * @param distance Detected distance
 * @return True, if measurement was successful
 */
static bool measure_close_range(acc_detector_distance_handle_t *distance_handle,
                                acc_detector_distance_configuration_t distance_configuration,
                                bool *distance_detected, float *distance);


/**
 * Measure in the mid range sector
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @param distance_detected True, if a distance was detected
 * @param distance Detected distance
 * @return True, if measurement was successful
 */
static bool measure_mid_range(acc_detector_distance_handle_t *distance_handle,
                              acc_detector_distance_configuration_t distance_configuration,
                              bool *distance_detected, float *distance);


/**
 * Measure in the far range sector
 *
 * @param distance_handle Distance Detector handle
 * @param distance_configuration Distance Detector configuration
 * @param distance_detected True, if a distance was detected
 * @param distance Detected distance
 * @return True, if measurement was successful
 */
static bool measure_far_range(acc_detector_distance_handle_t *distance_handle,
                              acc_detector_distance_configuration_t distance_configuration,
                              bool *distance_detected, float *distance);


int acc_ref_app_tank_level(int argc, char *argv[]);


int acc_ref_app_tank_level(int argc, char *argv[])
{
	(void)argc;
	(void)argv;
	APP_LOG(TS_OFF, VLEVEL_M,"Acconeer software version %s\n", acc_version_get());

	const acc_hal_t *hal = acc_hal_integration_get_implementation();

	if (!acc_rss_activate(hal))
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Failed to activate RSS\n");
		return EXIT_FAILURE;
	}

	acc_detector_distance_configuration_t distance_configuration = acc_detector_distance_configuration_create();

	if (distance_configuration == NULL)
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Failed to create detector configuration\n");
		acc_rss_deactivate();
		return EXIT_FAILURE;
	}

	acc_detector_distance_handle_t distance_handle = acc_detector_distance_create(distance_configuration);

	if (distance_handle == NULL)
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Failed to create detector\n");
		acc_detector_distance_configuration_destroy(&distance_configuration);
		acc_rss_deactivate();
		return EXIT_FAILURE;
	}

	if (!record_backgrounds(&distance_handle, distance_configuration))
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Failed to calibrate detector\n");
		acc_detector_distance_configuration_destroy(&distance_configuration);
		acc_detector_distance_destroy(&distance_handle);
		acc_rss_deactivate();
		return EXIT_FAILURE;
	}

	// If there is a long time between measurements it is good to calibrate the sensor
	// before each measurement
	acc_detector_distance_destroy(&distance_handle);

	if (!sensor_calibration())
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Failed to calibrate sensor\n");
		return EXIT_FAILURE;
	}

	distance_handle = acc_detector_distance_create(distance_configuration);

	bool status = true;

	while (status)
	{
		float distance          = 0.0f;
		bool  distance_detected = false;

		APP_LOG(TS_OFF, VLEVEL_M,"Measure close range\n");
		status = measure_close_range(&distance_handle, distance_configuration, &distance_detected, &distance);
		if (status && !distance_detected)
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Measure mid range\n");
			status = measure_mid_range(&distance_handle, distance_configuration, &distance_detected, &distance);
		}

		if (status && !distance_detected)
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Measure far range\n");
			status = measure_far_range(&distance_handle, distance_configuration, &distance_detected, &distance);
		}

		if (!status)
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Measurement failed\n");
			break;
		}

		if (distance_detected)
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Peak at %u mm\n", (unsigned int)(distance * 1000));
		}
		else
		{
			APP_LOG(TS_OFF, VLEVEL_M,"No peak found\n");
		}

		//Add a call to a sleep function here to limit measurement update rate
	}

	acc_detector_distance_configuration_destroy(&distance_configuration);
	acc_detector_distance_destroy(&distance_handle);
	acc_rss_deactivate();

	return status ? EXIT_SUCCESS : EXIT_FAILURE;
}


bool sensor_calibration(void)
{
	acc_calibration_context_t calibration_context;

	if (!acc_rss_calibration_context_get(DEFAULT_SENSOR, &calibration_context))
	{
		return false;
	}

	if (!acc_rss_calibration_context_forced_set(DEFAULT_SENSOR, &calibration_context))
	{
		return false;
	}

	return true;
}


bool configure_close_range(acc_detector_distance_handle_t        *distance_handle,
                           acc_detector_distance_configuration_t distance_configuration)
{
	acc_detector_distance_configuration_sensor_set(distance_configuration, DEFAULT_SENSOR);
	acc_detector_distance_configuration_mur_set(distance_configuration, DEFAULT_CLOSE_RANGE_MUR);
	acc_detector_distance_configuration_requested_start_set(distance_configuration, DEFAULT_CLOSE_RANGE_START);
	acc_detector_distance_configuration_requested_length_set(distance_configuration, DEFAULT_CLOSE_RANGE_LENGTH);
	acc_detector_distance_configuration_receiver_gain_set(distance_configuration, close_range_gain);
	acc_detector_distance_configuration_maximize_signal_attenuation_set(distance_configuration,
	                                                                    DEFAULT_CLOSE_MAXIMIZE_SIGNAL_ATTENUATION);
	acc_detector_distance_configuration_service_profile_set(distance_configuration,
	                                                        DEFAULT_CLOSE_RANGE_SERVICE_PROFILE);
	acc_detector_distance_configuration_downsampling_factor_set(distance_configuration,
	                                                            DEFAULT_CLOSE_RANGE_DOWNSAMPLIG_FACTOR);
	acc_detector_distance_configuration_sweep_averaging_set(distance_configuration,
	                                                        DEFAULT_CLOSE_RANGE_SWEEP_AVERAGING);
	acc_detector_distance_configuration_threshold_type_set(distance_configuration,
	                                                       DEFAULT_CLOSE_RANGE_THRESHOLD_TYPE);
	acc_detector_distance_configuration_record_background_sweeps_set(distance_configuration,
	                                                                 DEFAULT_CLOSE_RANGE_RECORD_BACKGROUND_SWEEPS);
	acc_detector_distance_configuration_threshold_sensitivity_set(distance_configuration,
	                                                              DEFAULT_CLOSE_RANGE_THRESHOLD_SENSITIVITY);

	return acc_detector_distance_reconfigure(distance_handle, distance_configuration);
}


bool configure_mid_range(acc_detector_distance_handle_t        *distance_handle,
                         acc_detector_distance_configuration_t distance_configuration)
{
	acc_detector_distance_configuration_mur_set(distance_configuration, DEFAULT_MID_RANGE_MUR);
	acc_detector_distance_configuration_requested_start_set(distance_configuration, DEFAULT_MID_RANGE_START);
	acc_detector_distance_configuration_requested_length_set(distance_configuration, DEFAULT_MID_RANGE_LENGTH);
	acc_detector_distance_configuration_receiver_gain_set(distance_configuration, mid_range_gain);
	acc_detector_distance_configuration_maximize_signal_attenuation_set(distance_configuration,
	                                                                    DEFAULT_MID_MAXIMIZE_SIGNAL_ATTENUATION);
	acc_detector_distance_configuration_service_profile_set(distance_configuration,
	                                                        DEFAULT_MID_RANGE_SERVICE_PROFILE);
	acc_detector_distance_configuration_downsampling_factor_set(distance_configuration,
	                                                            DEFAULT_MID_RANGE_DOWNSAMPLING_FACTOR);
	acc_detector_distance_configuration_sweep_averaging_set(distance_configuration,
	                                                        DEFAULT_MID_RANGE_SWEEP_AVERAGING);
	acc_detector_distance_configuration_threshold_type_set(distance_configuration,
	                                                       DEFAULT_MID_RANGE_THRESHOLD_TYPE);
	acc_detector_distance_configuration_record_background_sweeps_set(distance_configuration,
	                                                                 DEFAULT_MID_RANGE_RECORD_BACKGROUND_SWEEPS);
	acc_detector_distance_configuration_threshold_sensitivity_set(distance_configuration,
	                                                              DEFAULT_MID_RANGE_THRESHOLD_SENSITIVITY);

	return acc_detector_distance_reconfigure(distance_handle, distance_configuration);
}


bool configure_far_range(acc_detector_distance_handle_t        *distance_handle,
                         acc_detector_distance_configuration_t distance_configuration)
{
	acc_detector_distance_configuration_mur_set(distance_configuration, DEFAULT_FAR_RANGE_MUR);
	acc_detector_distance_configuration_requested_start_set(distance_configuration, DEFAULT_FAR_RANGE_START);
	acc_detector_distance_configuration_requested_length_set(distance_configuration, DEFAULT_FAR_RANGE_LENGTH);
	acc_detector_distance_configuration_receiver_gain_set(distance_configuration, DEFAULT_FAR_RANGE_GAIN);
	acc_detector_distance_configuration_maximize_signal_attenuation_set(distance_configuration,
	                                                                    DEFAULT_FAR_MAXIMIZE_SIGNAL_ATTENUATION);
	acc_detector_distance_configuration_service_profile_set(distance_configuration,
	                                                        DEFAULT_FAR_RANGE_SERVICE_PROFILE);
	acc_detector_distance_configuration_downsampling_factor_set(distance_configuration,
	                                                            DEFAULT_FAR_RANGE_DOWNSAMPLING_FACTOR);
	acc_detector_distance_configuration_sweep_averaging_set(distance_configuration,
	                                                        DEFAULT_FAR_RANGE_SWEEP_AVERAGING);
	acc_detector_distance_configuration_threshold_type_set(distance_configuration,
	                                                       DEFAULT_FAR_RANGE_THRESHOLD_TYPE);
	acc_detector_distance_configuration_threshold_sensitivity_set(distance_configuration,
	                                                              DEFAULT_FAR_RANGE_THRESHOLD_SENSITIVITY);
	acc_detector_distance_configuration_cfar_threshold_guard_set(distance_configuration,
	                                                             DEFAULT_FAR_RANGE_CFAR_THRESHOLD_GUARD);
	acc_detector_distance_configuration_cfar_threshold_window_set(distance_configuration,
	                                                              DEFAULT_FAR_RANGE_CFAR_THRESHOLD_WINDOW);

	return acc_detector_distance_reconfigure(distance_handle, distance_configuration);
}


bool record_background(acc_detector_distance_handle_t *distance_handle,
                       acc_detector_distance_configuration_t distance_configuration,
                       float *range_gain, uint16_t *background, uint16_t background_length)
{
	acc_detector_distance_recorded_background_info_t recorded_background_info;
	bool                                             status                  = true;
	bool                                             previous_gain_saturated = true;

	float gain = acc_detector_distance_configuration_receiver_gain_get(distance_configuration);

	recorded_background_info.data_saturated = true;

	while (status && gain > 0)
	{
		acc_detector_distance_configuration_receiver_gain_set(distance_configuration, gain);

		status = acc_detector_distance_reconfigure(distance_handle, distance_configuration);

		if (status)
		{
			status = acc_detector_distance_record_background(*distance_handle, background, background_length,
			                                                 &recorded_background_info);

			if (!status || (!previous_gain_saturated && !recorded_background_info.data_saturated))
			{
				break;
			}

			previous_gain_saturated = recorded_background_info.data_saturated;

			gain = gain - GAIN_STEP;
		}
	}

	if (status && recorded_background_info.data_saturated)
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Unable to record background without data saturation\n");
		status = false;
	}

	*range_gain = gain;

	return status;
}


bool record_backgrounds(acc_detector_distance_handle_t        *distance_handle,
                        acc_detector_distance_configuration_t distance_configuration)
{
	acc_detector_distance_metadata_t metadata;

	if (!configure_close_range(distance_handle, distance_configuration))
	{
		return false;
	}

	if (!acc_detector_distance_metadata_get(*distance_handle, &metadata))
	{
		return false;
	}

	close_background_length = metadata.background_length;
	APP_LOG(TS_OFF, VLEVEL_M,"Record close range\n");

	if (!record_background(distance_handle, distance_configuration, &close_range_gain,
	                       close_background, close_background_length))
	{
		return false;
	}

	if (!configure_mid_range(distance_handle, distance_configuration))
	{
		return false;
	}

	if (!acc_detector_distance_metadata_get(*distance_handle, &metadata))
	{
		return false;
	}

	mid_background_length = metadata.background_length;
	APP_LOG(TS_OFF, VLEVEL_M,"Record mid range\n");

	if (!record_background(distance_handle, distance_configuration, &mid_range_gain,
	                       mid_background, mid_background_length))
	{
		return false;
	}

	return true;
}


bool measurement(acc_detector_distance_handle_t        *distance_handle,
                 acc_detector_distance_configuration_t distance_configuration,
                 acc_detector_distance_result_t        *result,
                 acc_detector_distance_result_info_t   *result_info)
{
	float gain = acc_detector_distance_configuration_receiver_gain_get(distance_configuration);

	result_info->data_saturated = true;

	while (gain > 0)
	{
		if (!acc_detector_distance_activate(*distance_handle))
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Failed to activate detector\n");
			return false;
		}

		if (!acc_detector_distance_get_next(*distance_handle, result, 1, result_info))
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Failed to get next from detector\n");
			return false;
		}

		if (!acc_detector_distance_deactivate(*distance_handle))
		{
			APP_LOG(TS_OFF, VLEVEL_M,"Failed to deactivate detector\n");
			return false;
		}

		if (!result_info->data_saturated || gain < GAIN_STEP)
		{
			break;
		}

		gain = gain - GAIN_STEP;

		acc_detector_distance_configuration_receiver_gain_set(distance_configuration, gain);

		if (!acc_detector_distance_reconfigure(distance_handle, distance_configuration))
		{
			return false;
		}
	}

	if (result_info->data_saturated)
	{
		APP_LOG(TS_OFF, VLEVEL_M,"Unable to measure without data saturation\n");
		return false;
	}

	return true;
}


bool measure_close_range(acc_detector_distance_handle_t *distance_handle,
                         acc_detector_distance_configuration_t distance_configuration,
                         bool *distance_detected, float *distance)
{
	*distance_detected = false;
	acc_detector_distance_result_info_t result_info;
	acc_detector_distance_result_t      result;

	if (!configure_close_range(distance_handle, distance_configuration))
	{
		return false;
	}

	if (!acc_detector_distance_set_background(*distance_handle, close_background, close_background_length))
	{
		return false;
	}

	if (!measurement(distance_handle, distance_configuration, &result, &result_info))
	{
		return false;
	}

	if (result_info.measurement_sample_above_threshold)
	{
		*distance_detected = true;
		*distance          = result_info.closest_detection_m;
	}

	return true;
}


bool measure_mid_range(acc_detector_distance_handle_t *distance_handle,
                       acc_detector_distance_configuration_t distance_configuration,
                       bool *distance_detected, float *distance)
{
	*distance_detected = false;
	acc_detector_distance_result_info_t result_info;
	acc_detector_distance_result_t      result;

	if (!configure_mid_range(distance_handle, distance_configuration))
	{
		return false;
	}

	if (!acc_detector_distance_set_background(*distance_handle, mid_background, mid_background_length))
	{
		return false;
	}

	if (!measurement(distance_handle, distance_configuration, &result, &result_info))
	{
		return false;
	}

	if (result_info.number_of_peaks > 0)
	{
		*distance_detected = true;
		*distance          = result.distance_m;
	}

	return true;
}


bool measure_far_range(acc_detector_distance_handle_t *distance_handle,
                       acc_detector_distance_configuration_t distance_configuration,
                       bool *distance_detected, float *distance)
{
	*distance_detected = false;
	acc_detector_distance_result_info_t result_info;
	acc_detector_distance_result_t      result;

	if (!configure_far_range(distance_handle, distance_configuration))
	{
		return false;
	}

	if (!measurement(distance_handle, distance_configuration, &result, &result_info))
	{
		return false;
	}

	if (result_info.number_of_peaks > 0)
	{
		*distance_detected = true;
		*distance          = result.distance_m;
	}

	return true;
}