//******************************************** Включаемые файлы *********************************************

#include "flash_mem.h"
#include "lmcal.h"
#include "dbg_printf.h"
#include "fast_crc.h"


//******************************************* Локальные константы *******************************************

//  Признак правильной записи сетевых настроек
#define FM_SIGN                     (0x5349474E) // "SIGN"

// Размер структуры записи
#define FM_US_DATA_SIZE              (sizeof(fm_user_settings_data_t))
#define FM_FM_DATA_SIZE              (sizeof(fm_factory_mem_data_t))
// #define FM_CBR_DATA_SIZE             (FM_CBR_SIZE)
// #define FM_USER_DATA_SIZE            (FM_USR_DATA_SIZE)

#define FM_US_HDR_SIZE               (sizeof(fm_user_settings_hdr_t))
#define FM_FM_HDR_SIZE               (sizeof(fm_factory_mem_hdr_t))
// #define FM_CBR_HDR_SIZE              (sizeof(fm_cbr_mem_hdr_t))



//******************************************* Проверка параметров *******************************************


//******************************************** Определения типов ********************************************

typedef struct 
{
    uint32_t sign;
    uint32_t size;
    char     passwd[FM_PASSWD_SIZE];
    fm_user_settings_data_t data;
    uint32_t crc;
} fm_user_settings_hdr_t;


typedef struct 
{
    uint32_t sign;
    uint32_t size;
    char     passwd[FM_PASSWD_SIZE];
    fm_factory_mem_data_t data;
    uint32_t crc;
} fm_factory_mem_hdr_t;


// typedef struct 
// {
//     uint32_t sign;
//     uint32_t size;
//     char     passwd[FM_PASSWD_SIZE];
//     uint8_t  data[FM_CBR_SIZE];
//     uint32_t crc;
// } fm_cbr_mem_hdr_t;



//************************************ Глобальные (публичные) переменные ************************************

// Указатель на пользовательские настройки
fm_user_settings_data_t* fm_user_settings_data = NULL;

// Указатель на заводскую информацию
fm_factory_mem_data_t* fm_factory_mem_data = NULL;

// Указатель на калибровки
uint8_t* fm_cbr_data = NULL;

// Указатель на общие данные (пользовательскую память)
uint8_t* fm_cmn_data = NULL;


//************************************* Локальные (приватные) переменные ************************************

static bool b_init_done = false;

// Адрес структур с настройками 
static fm_user_settings_hdr_t* st_us_hdr = (fm_user_settings_hdr_t*)FM_USER_SETTINGS_ADDR;
static fm_factory_mem_hdr_t* st_fm_hdr   = (fm_factory_mem_hdr_t*)FM_FACTORY_MEM_ADDR;
static uint8_t* st_cbr_hdr               = (uint8_t*)FM_CBR_MEM_ADDR;
static uint8_t* st_cmn_hdr               = (uint8_t*)FM_CMN_MEM_ADDR;


//***********************************************************************************************************
//******************************** Определения локальных (приватных) функций ********************************
//***********************************************************************************************************

static bool f_is_password_set(char* password)
{
    bool res = false;
    const char null_value = 0xFF;

    // Проверка на нулевое значение пароля
    // Примечание - Последний символ пароля всегда = 0
    for (u16 i = 0; i < (FM_PASSWD_SIZE - 1); i++)
    {
        if (null_value != password[i])
        {
            res = true;
            break;
        }
    }

    return res;
}


static void f_write_data(lmcal_flash_channel_t channel, u32 offset, u8* src, u32 size, bool do_erase)
{
    if (do_erase == true)
    {
        lmcal_flash_erase(channel, offset, size, NULL);
    }
    lmcal_flash_write(channel, offset, src, size, NULL);
}


static void f_erase_data(lmcal_flash_channel_t channel, u32 offset, u32 size)
{
    lmcal_cpu_interrupt_enable(false);
    lmcal_flash_erase(channel, offset, size, NULL);
    lmcal_cpu_interrupt_enable(true);
}


static fun_res_t f_read_user_settings(void)
{
    fun_res_t err = ERR_OK;
    uint32_t crc, crc2;

    err = L_ASSERT(st_us_hdr->sign == FM_SIGN);
    L_CHECK_ERR(err);
    err = L_ASSERT(sizeof(fm_user_settings_data_t) >= (st_us_hdr->size));
    L_CHECK_ERR(err);

    // Чтение сектора памяти, проверка его валидности и передача информации в другие модули
    crc = CRC32_Block8(0, (uint8_t*)(&st_us_hdr->data), (uint32_t)(st_us_hdr->size));
    crc2 = st_us_hdr->crc;

    if(crc == crc2)
    {
        fm_user_settings_data = &st_us_hdr->data;
    }
    else
    {
        err = ERR_NOT_IMPLEMENTED;
        DBG_LOGE("CRC error! \n");
    }
    
    L_EXIT;
    if (ERR_OK != err)
    {
        fm_user_settings_data = NULL;
        DBG_LOGE("User settings are invalid!\n");
    }

    return err;
}



static fun_res_t f_read_factory_mem(void)
{
    fun_res_t err = ERR_OK;
    uint32_t crc, crc2;

    err = L_ASSERT(st_fm_hdr->sign == FM_SIGN);
    L_CHECK_ERR(err);
    err = L_ASSERT(sizeof(fm_factory_mem_data_t) >= (st_fm_hdr->size));
    L_CHECK_ERR(err);

    // Чтение сектора памяти, проверка его валидности и передача информации в другие модули
    crc = CRC32_Block8(0, (uint8_t*)(&st_fm_hdr->data), (uint32_t)(st_fm_hdr->size));
    crc2 = st_fm_hdr->crc;

    if(crc == crc2)
    {
        fm_factory_mem_data = &st_fm_hdr->data;
    }
    else
    {
        err = ERR_NOT_IMPLEMENTED;
        DBG_LOGE("CRC error! \n");
    }
    
    L_EXIT;
    if (ERR_OK != err)
    {
        fm_factory_mem_data = NULL;
        DBG_LOGE("Factory memory are invalid!\n");
    }

    return err;
}



static fun_res_t f_read_cbr_mem(void)
{
    fun_res_t err = ERR_OK;
    
    fm_cbr_data = st_cbr_hdr;
    
    return err;
}



static fun_res_t f_read_cmn_mem(void)
{
    fun_res_t err = ERR_OK;
    
    fm_cmn_data = st_cmn_hdr;
    
    return err;
}


//***********************************************************************************************************
//******************************* Определения глобальных (публичных) функций ********************************
//***********************************************************************************************************

void fm_read(void)
{
    fun_res_t err = ERR_OK;

    err = f_read_user_settings();
    if (err != ERR_OK)
    {
        DBG_LOGI("user_settings is not init!\n");    
    }
    
    err = f_read_factory_mem();
    if (err != ERR_OK)
    {
        DBG_LOGI("factory_mem is not init!\n");    
    }

    err = f_read_cbr_mem();
    if (err != ERR_OK)
    {
        DBG_LOGI("cbr_mem is not init!\n");
    }

    err = f_read_cmn_mem();
    if (err != ERR_OK)
    {
        DBG_LOGI("cmn_mem is not init!\n");
    }
    
    // Поддержка команд TCP
    extern void fm_tcp_init(void);
    fm_tcp_init();

    b_init_done = true;
}



fun_res_t fm_write_user_data(fm_user_settings_data_t* src, 
                             fm_us_format_t format,
                             const char* cur_password,
                             const char* new_password)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(b_init_done == true);
    L_CHECK_ERR(err);
    err = L_ASSERT(src != NULL);
    L_CHECK_ERR(err);

    // Проверка внутренней структуры
    if (fm_user_settings_data != NULL)
    {
        if (format == FM_US_FORMAT_WRITE_ALL)
        {
            // Текущий пароль установлен?
            bool password_is_set = f_is_password_set(st_us_hdr->passwd);

            // Проверка пароля
            if ((password_is_set == true) && 
                (strncmp(cur_password, st_us_hdr->passwd, FM_PASSWD_SIZE) != 0))
            {
                // Записанный пароль не совпал с аргументом (даже если передан NULL)
                err = ERR_INVALID_ARGUMENT;
                DBG_LOGE("Incorrect password!\n");
                L_GOTO_EXIT;
            }
        }
    }
    else
    {
        // Структура данных пуста или повреждена. Пароль не проверяем.
        if (format == FM_US_FORMAT_WRITE_ONLY_FLAGS)
        {
            // Нельзя писать только флаги, когда структура пуста
            err = ERR_RUNTIME_ERROR;
            L_GOTO_EXIT;
        }
    }

    // Запись
    fm_user_settings_hdr_t st_tmp;

    st_tmp.sign = FM_SIGN;
    st_tmp.size = FM_US_DATA_SIZE;

    strncpy(st_tmp.passwd, 
            ((new_password != NULL) ? new_password : st_us_hdr->passwd), 
            (FM_PASSWD_SIZE - 1));
    st_tmp.passwd[FM_PASSWD_SIZE] = 0;
    
    if (format == FM_US_FORMAT_WRITE_ALL)
    {
        memcpy(&st_tmp.data, src, (uint32_t)(st_tmp.size));
    }
    else if (format == FM_US_FORMAT_WRITE_ONLY_FLAGS)
    {
        memcpy(&st_tmp.data, fm_user_settings_data, (uint32_t)(st_tmp.size));
        st_tmp.data.flags = src->flags;
    }

    uint32_t crc = CRC32_Block8(0, (uint8_t*)(&st_tmp.data), (uint32_t)(st_tmp.size));
    st_tmp.crc = crc;

    // Запись
    f_write_data(FM_CH_USER_MEM, 0, (u8*)(&st_tmp), FM_US_HDR_SIZE, true);

    // Обновление информации
    f_read_user_settings();

    L_EXIT;
    return err;
}



fun_res_t fm_write_factory_data(fm_factory_mem_data_t* src, 
                                const char* cur_password,
                                const char* new_password)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(b_init_done == true);
    L_CHECK_ERR(err);
    err = L_ASSERT(src != NULL);
    L_CHECK_ERR(err);

    // Проверка внутренней структуры
    if (fm_factory_mem_data != NULL)
    {
        // Текущий пароль установлен?
        bool password_is_set = f_is_password_set(st_fm_hdr->passwd);

        // Проверка пароля
        if ((password_is_set == true) && 
            (strncmp(cur_password, st_fm_hdr->passwd, FM_PASSWD_SIZE) != 0))
        {
            // Записанный пароль не совпал с аргументом (даже если передан NULL)
            err = ERR_INVALID_ARGUMENT;
            DBG_LOGE("Incorrect password!\n");
            L_GOTO_EXIT;
        }
    }
    else
    {
        // Структура данных пуста или повреждена. Пароль не проверяем.
    }

    // Запись
    fm_factory_mem_hdr_t st_tmp;

    st_tmp.sign = FM_SIGN;
    st_tmp.size = FM_FM_DATA_SIZE;

    strncpy(st_tmp.passwd, 
            ((new_password != NULL) ? new_password : st_fm_hdr->passwd), 
            (FM_PASSWD_SIZE - 1));
    st_tmp.passwd[FM_PASSWD_SIZE] = 0;
    
    memcpy(&st_tmp.data, src, (uint32_t)(st_tmp.size));

    uint32_t crc = CRC32_Block8(0, (uint8_t*)(&st_tmp.data), (uint32_t)(st_tmp.size));
    st_tmp.crc = crc;

    // Запись
    f_write_data(FM_CH_FACTORY_MEM, 0, (u8*)(&st_tmp), FM_FM_HDR_SIZE, true);

    // Обновление информации
    f_read_factory_mem();

    L_EXIT;
    return err;
}



fun_res_t fm_write_cbr_data(uint8_t* src,
                            uint16_t addr,
                            uint16_t size)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(b_init_done == true);
    L_CHECK_ERR(err);
    err = L_ASSERT(src != NULL);
    L_CHECK_ERR(err);
    err = L_ASSERT(size != 0);
    L_CHECK_ERR(err);
    err = L_ASSERT(FM_CBR_MEM_SIZE >= size);
    L_CHECK_ERR(err);
    
    // Запись
    f_write_data(FM_CH_CALIB_MEM, addr, src, size, false);

    L_EXIT;
    return err;
}



fun_res_t fm_write_cmn_data(uint8_t* src,
                            uint16_t addr,
                            uint16_t size)
{
    fun_res_t err = ERR_OK;

    err = L_ASSERT(b_init_done == true);
    L_CHECK_ERR(err);
    err = L_ASSERT(src != NULL);
    L_CHECK_ERR(err);
    err = L_ASSERT(size != 0);
    L_CHECK_ERR(err);
    err = L_ASSERT(FM_CMN_MEM_SIZE >= size);
    L_CHECK_ERR(err);

    // Запись
    f_write_data(FM_CH_CMN_MEM, addr, src, size, false);

    L_EXIT;
    return err;
}



fun_res_t fm_erase_user_data(const char* cur_password)
{
    fun_res_t err = ERR_OK;

    // Проверка внутренней структуры
    if (fm_user_settings_data != NULL)
    {
        // Текущий пароль установлен?
        bool password_is_set = f_is_password_set(st_us_hdr->passwd);

        // Проверка пароля
        if ((password_is_set == true) && 
            (strncmp(cur_password, st_us_hdr->passwd, FM_PASSWD_SIZE) != 0))
        {
            // Записанный пароль не совпал с аргументом (даже если передан NULL)
            err = ERR_INVALID_ARGUMENT;
            DBG_LOGE("Incorrect password!\n");
            L_GOTO_EXIT;
        }
    }

    f_erase_data(FM_CH_USER_MEM, 0, FM_US_HDR_SIZE);

    // Обновление информации
    f_read_user_settings();

    L_EXIT;
    return err;
}



fun_res_t fm_erase_factory_data(const char* cur_password)
{
    fun_res_t err = ERR_OK;

    // Проверка внутренней структуры
    if (fm_factory_mem_data != NULL)
    {
        // Текущий пароль установлен?
        bool password_is_set = f_is_password_set(st_fm_hdr->passwd);

        // Проверка пароля
        if ((password_is_set == true) && 
            (strncmp(cur_password, st_fm_hdr->passwd, FM_PASSWD_SIZE) != 0))
        {
            // Записанный пароль не совпал с аргументом (даже если передан NULL)
            err = ERR_INVALID_ARGUMENT;
            DBG_LOGE("Incorrect password!\n");
            L_GOTO_EXIT;
        }
    }

    f_erase_data(FM_CH_FACTORY_MEM, 0, FM_FM_HDR_SIZE);

    // Обновление информации
    f_read_factory_mem();

    L_EXIT;
    return err;
}



fun_res_t fm_erase_cbr_data(uint16_t addr, uint16_t size)
{
    fun_res_t err = ERR_OK;

    f_erase_data(FM_CH_CALIB_MEM, addr, size);

    return err;
}



fun_res_t fm_erase_cmn_data(uint16_t addr, uint16_t size)
{
    fun_res_t err = ERR_OK;

    f_erase_data(FM_CH_CMN_MEM, addr, size);

    return err;
}


//*********************************************** КОНЕЦ ФАЙЛА ***********************************************
