/*******************************************************************************
 TEST medi50  Dec 23
 ******************************************************************************/

/**
 * @file  fstorage.c
 * @brief FDS(Flash Data Storage) 기반 설정 저장 모듈
 *
 * 외부 EEPROM을 대체하여 nRF52840 내장 플래시에 장치 설정을 저장/로드한다.
 *
 * [레코드 관리]
 *   - CONFIG_FILE = 0x8010, CONFIG_REC_KEY = 0x7010 으로 단일 레코드를 관리한다.
 *
 * [config_data_t 구조체 필드]
 *   - magic(4B)            : 포맷 확인용 매직 넘버 (0x20231226 -> 0x20260318(기본값 재생성을 위해 매직넘버 변경))
 *   - hw_no(12B)           : 하드웨어 버전 (기본값: "")
 *   - serial_no(12B)       : 시리얼 번호 (기본값: "VB026030000")
 *   - passkey(6B)          : BLE 페어링용 정적 패스키(기본값: "123456")
 *   - bond_data_delete(1B) : 본딩 데이터 삭제 플래그(기본값: 1)
 *   - reset_status(1B)     : 리셋 상태 값(기본값: 99)
 *   - life_cycle(4B)       : 장치 사용 횟수(기본값: 0)
 *   - piezo_freq_option    : Piezo 송신 펄스 주파수(기본값: 1=2.1MHz)
 *   - piezo_cycles         : Piezo 송신 펄스 사이클 수 (기본값: 7)
 *   - piezo_averaging      : Piezo 채널당 반복 측정 횟수 (기본값: 5)
 *   - piezo_delay_us       : Piezo 송신 펄스 출력 후 ADC 시작 시까지 대기시간(us) (기본값: 10)
 *   - piezo_num_samples    : Piezo 측정 ADC 샘플 개수 (기본값: 100)
 *
 * [매직 넘버 검증]
 *   - 플래시에서 로드한 데이터의 magic 값이 0x20231226과 일치하는지 확인하여
 *     유효한 설정인지 판별한다. 불일치 시 기본값으로 초기화한다.
 *
 * [FDS 이벤트 후처리]
 *   - FDS 쓰기/업데이트 완료 이벤트 수신 후, 대기 중인 후처리를 수행한다:
 *     전원 OFF (go_device_power_off), 슬립 진입 (go_sleep_mode_enter),
 *     시스템 리셋 (go_NVIC_SystemReset)
 */

#include "sdk_config.h"

#include <string.h>

#include "app_error.h"
#include "boards.h"
#include "nrf_fstorage.h"
#include "nrf_soc.h"
#include "nrf_strerror.h"
#include "sdk_config.h"
#include "nrf_fstorage_sd.h"
#include "nrf_delay.h"
#include "ble_gap.h"
#include "fds.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#include "fstorage.h"
#include "nrf_pwr_mgmt.h"
#include "main.h"
#include "debug_print.h"


/* FDS 레코드 식별자: 파일 ID와 레코드 키로 단일 설정 레코드를 관리 */

#define CONFIG_FILE     (0x8010)
#define CONFIG_REC_KEY  (0x7010)

/* 매직 넘버: 플래시에 저장된 데이터가 유효한 설정인지 판별하는 데 사용 */
#define CONFIG_MAGIC_NUMBER_VALUE          (0x20260319)

/* 전역 설정 데이터 구조체 인스턴스 */
config_data_t m_config;

/* FDS 쓰기 완료 후 수행할 후처리 플래그 (main.c에서 선언) */
extern bool go_device_power_off;   /* 전원 OFF 요청 */
extern bool go_sleep_mode_enter;   /* 슬립 모드 진입 요청 */
extern bool go_NVIC_SystemReset;   /* 시스템 리셋 요청 */

/* FDS 초기화 완료 여부 플래그 (fds_evt_handler에서 true로 설정) */
static bool volatile m_fds_initialized;

/* FDS 쓰기 진행 중 플래그: true이면 쓰기 완료 대기 중 */
bool fds_flag_write = false;

/* FDS에 기록할 레코드 템플릿 (m_config 데이터를 가리킴) */
static fds_record_t const m_dummy_record =
{
    .file_id           = CONFIG_FILE,
    .key               = CONFIG_REC_KEY,
    .data.p_data       = (void const *)&m_config,
    /* The length of a record is always expressed in 4-byte units (words). */
    .data.length_words = (sizeof(m_config) + 3) / sizeof(uint32_t),
};


/* 기본 설정값 상수 */
int8_t	reset_status_dflt = 99;              /* 리셋 상태 기본값 */
uint8_t static_passkey_dflt[6] = "123456";   /* BLE 패스키 기본값 */

/**
 * @brief 기본 설정값 초기화
 *
 * m_config 구조체의 각 필드를 공장 초기값으로 설정한다.
 * 플래시에 유효한 설정이 없거나 매직 넘버가 불일치할 때 호출된다.VB0HW0000
 */
void fds_default_value_set(void)
{
	/* HW Number - empty (set via BLE command) */
	memset(m_config.hw_no, 0, 12);

	/* Serial Number - default from FIRMWARE_SERIAL_NO */
	memset(m_config.serial_no, 0, 12);
	memcpy(m_config.serial_no, "VB026030000", 11);

	/* Static Passkey */
	memcpy(m_config.static_passkey, static_passkey_dflt, 6);

	/* Bond data delete */
	m_config.bond_data_delete = 1;

	/* Reset status */
	m_config.reset_status = reset_status_dflt;

	/* Device usage count */
	m_config.life_cycle  = 0;

	/* 피에조 캡처 파라미터 기본값 */
	m_config.piezo_freq_option = 1;      /* 2.1MHz */
	m_config.piezo_delay_us    = 10;     /* 버스트 후 10us */
	m_config.piezo_num_samples = 100;    /* 100샘플 */
	m_config.piezo_cycles      = 7;      /* 7사이클 */
	m_config.piezo_averaging   = 5;      /* 5회 평균화 */
}


/* 마지막 FDS 이벤트 ID 저장 (디버깅용) */
static volatile uint8_t fds_last_evt = 0xFF;

/**
 * @brief FDS 이벤트 콜백 핸들러
 *
 * FDS 내부에서 비동기 작업이 완료될 때 호출된다.
 * - FDS_EVT_INIT   : FDS 초기화 완료 → m_fds_initialized 플래그 설정
 * - FDS_EVT_WRITE  : 새 레코드 쓰기 완료 → fds_flag_write 해제
 * - FDS_EVT_UPDATE : 레코드 업데이트 완료 → fds_flag_write 해제 후
 *                    대기 중인 전원 OFF / 슬립 진입 / 시스템 리셋 수행
 * - FDS_EVT_DEL_RECORD / FDS_EVT_DEL_FILE / FDS_EVT_GC : 현재 미사용
 */
static void fds_evt_handler( fds_evt_t const *p_evt )
{
    fds_last_evt = p_evt->id;

    switch( p_evt->id )
    {
        case FDS_EVT_INIT:
            if( p_evt->result == NRF_SUCCESS )
            {
                m_fds_initialized = true;
            }
            break;

        case FDS_EVT_WRITE:
            {
                fds_flag_write = false;
            }
            break;

		case FDS_EVT_UPDATE:
			{
				fds_flag_write = false;

                if(go_device_power_off == true) {
                    /* After flash writing completed, System Power Off */
                    device_power_off();
                }
                if(go_sleep_mode_enter == true) {
                    /* After flash writing completed, System go to Sleep Mode */
                    sleep_mode_enter();
                }
                if(go_NVIC_SystemReset == true) {
                    /* After flash writing completed, System Reset */
                    DBG_PRINTF("Off FDS_EVENT\r\n");
                    NVIC_SystemReset();
                }
			}
			break;

		case FDS_EVT_DEL_RECORD:
			break;

		case FDS_EVT_DEL_FILE:
			break;

		case FDS_EVT_GC:
			break;

		default:
            break;
    }
}


/**
 * @brief FDS 초기화 완료 대기
 *
 * m_fds_initialized 플래그가 true가 될 때까지 대기한다.
 * 최대 3초(3000ms) 타임아웃이 설정되어 있으며,
 * 타임아웃 시 에러 로그를 출력하고 반환한다.
 */
static void wait_for_fds_ready( void )
{
    uint32_t timeout = 0;
    while( !m_fds_initialized )
    {
        nrf_pwr_mgmt_run();
        nrf_delay_ms(1);
        timeout++;
        if (timeout > 3000)  /* 3 second timeout */
        {
            DBG_PRINTF("[FDS] TIMEOUT!\r\n");
            break;
        }
    }
}


/**
 * @brief FDS에서 설정 로드
 *
 * 플래시에서 CONFIG_FILE/CONFIG_REC_KEY 레코드를 검색하여 m_config에 로드한다.
 *
 * 동작 흐름:
 * 1. fds_record_find()로 레코드 검색 (실패 시 최대 10회 재시도, 100ms 간격)
 * 2. 레코드 발견 시:
 *    - fds_record_open()으로 열기 (CRC 에러 시 삭제 후 기본값으로 재생성)
 *    - 데이터를 m_config로 복사
 *    - 매직 넘버 불일치 시 기존 레코드 삭제 → 기본값 설정 → 재기록
 * 3. 레코드 미발견 시:
 *    - 기본값으로 새 레코드 생성 후 다시 로드
 */
void config_load( void )
{
    ret_code_t rc;
    fds_record_desc_t desc = { 0 };
    fds_find_token_t  tok  = { 0 };
    uint8_t cfg_retry = 0;

 cfg_load_start:
    memset((char *)&desc, 0, sizeof(desc));
    memset((char *)&tok, 0, sizeof(tok));
    rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
    DBG_PRINTF("[FDS] find rc=%u\r\n", rc);

    /* FDS may not be fully ready yet - retry before writing defaults */
    if (rc != NRF_SUCCESS && cfg_retry < 10)
    {
        cfg_retry++;
        DBG_PRINTF("[FDS] retry %u/10\r\n", cfg_retry);
        nrf_delay_ms(100);
        goto cfg_load_start;
    }

    if( rc == NRF_SUCCESS )
    {
        /* A config file is in flash. Let's update it. */
        fds_flash_record_t config = { 0 };

        /* Open the record and read its contents. */
        rc = fds_record_open(&desc, &config);
        if (rc != NRF_SUCCESS)
        {
            /* CRC error or corrupt record - delete and use defaults */
            DBG_PRINTF("[FDS] open ERR=%u, deleting\r\n", rc);
            (void)fds_record_delete(&desc);
            fds_gc();
            fds_default_value_set();
            goto cfg_load_write_new;
        }

        /* Copy the configuration from flash into m_config. */
        memcpy(&m_config, config.p_data, sizeof(config_data_t));

        /* Close the record when done reading. */
        rc = fds_record_close(&desc);
        APP_ERROR_CHECK(rc);

        DBG_PRINTF("[FDS] magic=0x%08X (expect 0x%08X)\r\n", m_config.magic_number, CONFIG_MAGIC_NUMBER_VALUE);

        if( m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE )
        { // first init
            DBG_PRINTF("[FDS] FORMAT! overwriting with defaults\r\n");
            rc = fds_record_delete(&desc);
            APP_ERROR_CHECK(rc);
            m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;

			// default....
			fds_default_value_set();

			/* Write the updated record to flash. */
            rc = fds_record_update(&desc, &m_dummy_record);
            if( (rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH) )
            {
                rc = fds_gc();
                APP_ERROR_CHECK(rc);
            }
            else
            {
                APP_ERROR_CHECK(rc);
            }
            goto cfg_load_start;
        }
        DBG_PRINTF("[FDS] Loaded OK\r\n");
    }
    else
    {
        cfg_load_write_new:
        DBG_PRINTF("[FDS] New - writing defaults\r\n");
        /* System config not found (or corrupt); write a new one. */
        m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;

		// default....
		fds_default_value_set();

        fds_flag_write = true;
        rc = fds_record_write(&desc, &m_dummy_record);
        
        if (rc != NRF_SUCCESS)
        {
            DBG_PRINTF("[FDS] Write ERR=%u\r\n", rc);
            fds_flag_write = false;
        }

        while( fds_flag_write )
        {
            nrf_pwr_mgmt_run();
        }

        if( (rc != NRF_SUCCESS) && (rc == FDS_ERR_NO_SPACE_IN_FLASH) )
        {
            rc = fds_gc();
            APP_ERROR_CHECK(rc);
        }
        else
        {
            APP_ERROR_CHECK(rc);
        }

        NRF_LOG_FLUSH();
        goto cfg_load_start;
    }
}


/**
 * @brief 현재 설정을 FDS에 저장
 *
 * m_config의 내용을 플래시에 기록한다.
 *
 * 동작 흐름:
 * 1. 이전 FDS 쓰기 작업이 진행 중이면 최대 3초 대기
 * 2. 매직 넘버가 올바르지 않으면 보정
 * 3. 기존 레코드가 있으면 fds_record_update()로 갱신
 *    - 플래시 공간 부족 시 GC(가비지 컬렉션) 수행 후 재시도
 * 4. 기존 레코드가 없으면 fds_record_write()로 새로 생성
 *
 * 참고: 쓰기 완료는 fds_evt_handler()에서 비동기로 처리되며,
 *       완료 후 전원 OFF/슬립/리셋 등의 후처리가 수행될 수 있다.
 */
void config_save( void )
{
    ret_code_t rc;
    fds_record_desc_t desc = { 0 };
    fds_find_token_t  tok  = { 0 };

    DBG_PRINTF("[CFG_SAVE] start\r\n");

    /* Wait for any previous FDS operation to complete */
    if (fds_flag_write)
    {
        uint32_t wait_cnt = 0;

        DBG_PRINTF("[CFG_SAVE] waiting for prev FDS op...\r\n");
        while (fds_flag_write && wait_cnt < 3000)
        {
            nrf_pwr_mgmt_run();
            nrf_delay_ms(1);
            wait_cnt++;
        }
        if (fds_flag_write)
        {
            DBG_PRINTF("[CFG_SAVE] TIMEOUT! forcing flag clear\r\n");
            fds_flag_write = false;
        }
    }

    if( m_config.magic_number != (uint32_t)CONFIG_MAGIC_NUMBER_VALUE )
    {
        m_config.magic_number = CONFIG_MAGIC_NUMBER_VALUE;
    }

    memset((char *)&desc, 0, sizeof(desc));
    memset((char *)&tok, 0, sizeof(tok));
    rc = fds_record_find(CONFIG_FILE, CONFIG_REC_KEY, &desc, &tok);
    DBG_PRINTF("[CFG_SAVE] find rc=%u\r\n", rc);

    if( rc == NRF_SUCCESS )
    {
        fds_flag_write = true;
        rc = fds_record_update(&desc, &m_dummy_record);
        DBG_PRINTF("[CFG_SAVE] update rc=%u\r\n", rc);

        if( rc == FDS_ERR_NO_SPACE_IN_FLASH )
        {
            fds_flag_write = false;
            rc = fds_gc();
            DBG_PRINTF("[CFG_SAVE] gc rc=%u, retry\r\n", rc);
            fds_flag_write = true;
            rc = fds_record_update(&desc, &m_dummy_record);
            DBG_PRINTF("[CFG_SAVE] retry rc=%u\r\n", rc);
        }

        if( rc != NRF_SUCCESS )
        {
            DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
            fds_flag_write = false;
        }
    }
    else
    {
        DBG_PRINTF("[CFG_SAVE] not found, writing new\r\n");
        fds_flag_write = true;
        rc = fds_record_write(&desc, &m_dummy_record);
        DBG_PRINTF("[CFG_SAVE] write rc=%u\r\n", rc);
        
        if( rc != NRF_SUCCESS )
        {
            DBG_PRINTF("[CFG_SAVE] FAIL rc=%u\r\n", rc);
            fds_flag_write = false;
        }
    }

    DBG_PRINTF("[CFG_SAVE] done\r\n");
}


/**
 * @brief config_load()의 래퍼 함수
 *
 * 외부 모듈에서 설정 로드를 요청할 때 사용한다.
 */
void fs_set_value(void)
{
	config_load();
}

/**
 * @brief FDS 초기화
 *
 * 부팅 시 호출되어 FDS 모듈을 초기화한다.
 * 1. fds_register()로 이벤트 핸들러 등록
 * 2. fds_init()로 FDS 초기화 시작
 * 3. wait_for_fds_ready()로 초기화 완료 대기 (최대 3초)
 * 4. fds_stat()로 플래시 상태 확인
 */
void fs_storage_init(void)
{
    ret_code_t rc;

    /* Register first to receive an event when initialization is complete. */
    rc = fds_register(fds_evt_handler);
    APP_ERROR_CHECK(rc);

    rc = fds_init();
    APP_ERROR_CHECK(rc);

    /* Wait for fds to initialize. */
    wait_for_fds_ready();

    fds_stat_t stat = { 0 };
    rc = fds_stat(&stat);
    APP_ERROR_CHECK(rc);

    DBG_PRINTF("[FDS] OK\r\n");
}
