/*
 * challenge_nvs.c
 *
 *  Created on: 08.12.2022
 *      Author: jonas
 */

#include "challenge_nvs.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "McuUtility.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"

/* defines / constants */
#define TAG "CHALLENGE_NVS" /* tag for logging with ESP_LOG */
const char* NAMESPACE_NVS = "storage";
const char* ROBOT_MODE_KEY = "robot_mode";
const char* MQTT_IP_KEY = "mqtt_ip";

/* local vars */
bool initializedSuccessfully = false;
SemaphoreHandle_t semaphore = NULL;

/*! \brief Initialize the NVS storage. */
void Challenge_Nvs_Init(void){
	ESP_LOGI(TAG, "Initializing...");

	// initialize semaphore
	semaphore = xSemaphoreCreateRecursiveMutex();
	if(semaphore == NULL){
		ESP_LOGE(TAG, "Failed to initialize semaphore for Challenge NVS!");
	}else{
		vQueueAddToRegistry(semaphore, "challenge_nvs_semphr");
	}

	// Initialize NVS
	esp_err_t err = nvs_flash_init();
	if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
		// NVS partition was truncated and needs to be erased
		// Retry nvs_flash_init
		ESP_ERROR_CHECK(nvs_flash_erase());
		err = nvs_flash_init();
	}
	ESP_ERROR_CHECK( err );
	initializedSuccessfully = (err == ESP_OK);

	ESP_LOGI(TAG, "Initialized. Success=%s", initializedSuccessfully?"true":"false");
}

/*! \brief Deinitialize the NVS storage. */
void Challenge_Nvs_Deinit(void){
	ESP_LOGI(TAG, "Deinitializing...");
	esp_err_t err = nvs_flash_deinit();
	ESP_LOGI(TAG, "Deinitialized. Success=%s", err==ESP_OK?"true":"false");

	vSemaphoreDelete(semaphore);
}

/* HELPERS FOR SEMAPHORE */
static bool Challenge_Nvs_AquireMutex(void){
	if(xSemaphoreTakeRecursive(semaphore, pdMS_TO_TICKS(100)) != pdTRUE){
		/* timeout */
		ESP_LOGW(TAG, "Timemout while aquiring challenge NVS semaphore.");
		return false;
	}
	return true;
}

static void Challenge_Nvs_ReleaseMutex(void){
	xSemaphoreGive(semaphore);
}

bool Challenge_Nvs_StoreRobotModeToNVS(bool mode){
	bool success = false;

	if(Challenge_Nvs_AquireMutex()){
		// Open
		ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle... ");
		nvs_handle_t my_handle;
		esp_err_t err = nvs_open(NAMESPACE_NVS, NVS_READWRITE, &my_handle);
		if (err != ESP_OK) {
			ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
			return false;
		}

		// Write
		uint8_t value = mode?1:0;
		ESP_LOGI(TAG, "Updating robotMode in NVS ... ");
		err = nvs_set_u8(my_handle, ROBOT_MODE_KEY, value);
		if(err != ESP_OK){
			ESP_LOGE(TAG, "Failed to update robotMode in NVS. Error=(%s)", esp_err_to_name(err));
			success = false;
		}else{ // successfully set
			// Commit written value.
			// After setting any values, nvs_commit() must be called to ensure changes are written
			// to flash storage. Implementations may write to storage at other times,
			// but this is not guaranteed.
			ESP_LOGI(TAG, "Committing updates in NVS ... ");
			err = nvs_commit(my_handle);
			if(err != ESP_OK){
				ESP_LOGE(TAG, "Failed to commit to NVS. Error=(%s)", esp_err_to_name(err));
				success = false;
			}else{
				ESP_LOGI(TAG, "Successfully committed changes to NVS.");
				success = true;
			}
		}

		// Close
		nvs_close(my_handle);

		Challenge_Nvs_ReleaseMutex();
	}

	return success;
}

bool Challenge_Nvs_GetRobotModeFromNVS(bool *mode){
	bool success = false;
	bool notInitialized = false;

	if(Challenge_Nvs_AquireMutex()){
		// Open
		ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle... ");
		nvs_handle_t my_handle;
		esp_err_t err = nvs_open(NAMESPACE_NVS, NVS_READWRITE, &my_handle);
		if (err != ESP_OK) {
			ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
			return false;
		}

		// Read
		ESP_LOGI(TAG, "Reading robot mode from NVS ... ");
		uint8_t value = 0;
		err = nvs_get_u8(my_handle, ROBOT_MODE_KEY, &value);
		switch (err) {
			case ESP_OK:
				ESP_LOGI(TAG, "Reading robot mode done. Read robotMode = %i", value);
				*mode = (bool)(value==1?true:false); // set value at pointer location
				success = true;
				break;
			case ESP_ERR_NVS_NOT_FOUND:
				ESP_LOGI(TAG, "Reading robot mode done. The value is not initialized yet!");
				notInitialized = true;
				break;
			default :
				ESP_LOGI(TAG, "Error reading robot mode. Error=(%s)", esp_err_to_name(err));
				success = false;
		}

		// Close
		nvs_close(my_handle);

		if(notInitialized){
			success = Challenge_Nvs_StoreRobotModeToNVS(false);  // initialize to false
		}

		Challenge_Nvs_ReleaseMutex();
	}

	return success;
}


bool Challenge_Nvs_StoreBrokerIpToNVS(char* ip){
	bool success = false;

	if(Challenge_Nvs_AquireMutex()){
		// Open
		ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle... ");
		nvs_handle_t my_handle;
		esp_err_t err = nvs_open(NAMESPACE_NVS, NVS_READWRITE, &my_handle);
		if (err != ESP_OK) {
			ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
			return false;
		}

		// Write
		ESP_LOGI(TAG, "Updating mqtt broker IP in NVS ... ");
		err = nvs_set_str(my_handle, MQTT_IP_KEY, ip);
		if(err != ESP_OK){
			ESP_LOGE(TAG, "Failed to update mqtt broker IP in NVS. Error=(%s)", esp_err_to_name(err));
			success = false;
		}else{ // successfully set
			// Commit written value.
			// After setting any values, nvs_commit() must be called to ensure changes are written
			// to flash storage. Implementations may write to storage at other times,
			// but this is not guaranteed.
			ESP_LOGI(TAG, "Committing updates in NVS ... ");
			err = nvs_commit(my_handle);
			if(err != ESP_OK){
				ESP_LOGE(TAG, "Failed to commit to NVS. Error=(%s)", esp_err_to_name(err));
				success = false;
			}else{
				ESP_LOGI(TAG, "Successfully committed changes to NVS.");
				success = true;
			}
		}

		// Close
		nvs_close(my_handle);

		Challenge_Nvs_ReleaseMutex();
	}

	return success;
}

bool Challenge_Nvs_GetBrokerIpFromNVS(char *ip, size_t length){
	bool success = false;
	bool notInitialized = false;

	if(Challenge_Nvs_AquireMutex()){
		// Open
		ESP_LOGI(TAG, "Opening Non-Volatile Storage (NVS) handle... ");
		nvs_handle_t my_handle;
		esp_err_t err = nvs_open(NAMESPACE_NVS, NVS_READWRITE, &my_handle);
		if (err != ESP_OK) {
			ESP_LOGE(TAG, "Error (%s) opening NVS handle!\n", esp_err_to_name(err));
			return false;
		}

		// Read
		ESP_LOGI(TAG, "Reading mqtt broker IP from NVS ... ");
		size_t required_size;
		nvs_get_str(my_handle, MQTT_IP_KEY, NULL, &required_size);  // retrieve required size
		char* value = pvPortMalloc(required_size);
		err = nvs_get_str(my_handle, MQTT_IP_KEY, value, &required_size);
		switch (err) {
			case ESP_OK:
				ESP_LOGI(TAG, "Reading mqtt broker ip done. Read IP = %s", value);
				McuUtility_strcpy((unsigned char*)ip, length, (unsigned char *)value);  // copy ip to destination
				success = true;
				break;
			case ESP_ERR_NVS_NOT_FOUND:
				ESP_LOGI(TAG, "Reading mqtt broker ip done. The value is not initialized yet!");
				notInitialized = true;
				break;
			default :
				ESP_LOGI(TAG, "Error reading mqtt broker ip. Error=(%s)", esp_err_to_name(err));
				success = false;
		}

		// Close
		nvs_close(my_handle);

		if(notInitialized){
			success = Challenge_Nvs_StoreBrokerIpToNVS("10.0.1.1");  // initialize to default IP
		}

		Challenge_Nvs_ReleaseMutex();
	}

	return success;
}

