#include "app_timer.h"
#include "error.h"
#include "fds_flash.h"
#include "fstorage.h"
#include "nrf_delay.h"
#include "nrf_fstorage.h"
#include "nrf_fstorage_sd.h"
#include "nrf_fstorage_nvmc.h"
#include "nrf_log.h"
#include "nrf_soc.h"
#include "spi_flash.h"

#define FSTORAGE_PAGE_NUM    1
#define FSTORAGE_WAIT_CNT    1000

static bool _gFstorageInitialized = false;
static bool _gFstorageEraseDone   = true;
static bool _gFstorageWriteDone   = true;
static bool _gFstoragePageDirty   = true;
static fstorage_state_t _gFstorageState = FSTORAGE_STATE_UNKNOWN;
static uint8_t _gData[RECORD_LEN] = {0};

static void _fstorageEvtHandler(nrf_fstorage_evt_t * p_evt);

NRF_FSTORAGE_DEF(nrf_fstorage_t _gFstorage) =
{
    /* Set a handler for fstorage events. */
    .evt_handler = _fstorageEvtHandler,

    /* These below are the boundaries of the flash space assigned to this instance of fstorage.
     * You must set these manually, even at runtime, before nrf_fstorage_init() is called.
     * The function _fstorageGetEndAddr() can be used to retrieve the last address on the
     * last page of flash available to write data. */
    .start_addr = 0x7F000,
    .end_addr   = 0x7FFFF,
};

static void _fstorageEvtHandler(nrf_fstorage_evt_t * pEvt) {
    NRF_LOG_DEBUG("Evt e\n");
    switch (pEvt->id) {
        case NRF_FSTORAGE_EVT_WRITE_RESULT:
        {
            NRF_LOG_DEBUG("Evt w\n");
            _gFstorageWriteDone = true;
            _gFstorageState     = FSTORAGE_STATE_WRITTEN;
            if (pEvt->result != NRF_SUCCESS) {
               NRF_LOG_WARNING("fstorage writing failed %d bytes at address 0x%x.",
                            pEvt->len, pEvt->addr);
            } else {
                NRF_LOG_DEBUG("NRF_FSTORAGE_EVT_WRITE_RESULT ok");
                _gFstoragePageDirty = true;
            }
        } break;

        case NRF_FSTORAGE_EVT_ERASE_RESULT:
        {
            NRF_LOG_DEBUG("Evt er\n");
            _gFstorageEraseDone = true;
            _gFstorageState     = FSTORAGE_STATE_ERASED;
            if (pEvt->result != NRF_SUCCESS) {
                NRF_LOG_WARNING("fstorage erased failed %d page from address 0x%x.",
                                 pEvt->len, pEvt->addr);
            } else {
                NRF_LOG_DEBUG("NRF_FSTORAGE_EVT_ERASE_RESULT ok");
                _gFstoragePageDirty = false;
            }
        } break;

        default:
            break;
    }
     NRF_LOG_DEBUG("Evt x\n");
}

static uint32_t _fstorageGetEndAddr(void) {
    const uint32_t bootloaderAddr = BOOTLOADER_ADDRESS;
    const uint32_t pageSz         = NRF_FICR->CODEPAGESIZE;
    const uint32_t codeSz         = NRF_FICR->CODESIZE;

    return (bootloaderAddr != 0xFFFFFFFF ?
            bootloaderAddr : (codeSz * pageSz)-1);
}

static bool _fstorageIsEraseNeeded(void) {
    return _gFstoragePageDirty;
}    

static uint32_t _fstorageRoundUpU32(const uint32_t len) {
    if (len % sizeof(uint32_t)) {
        return (len + sizeof(uint32_t) - (len % sizeof(uint32_t)));
    }

    return len;
}

static void power_manage(void)
{
#ifdef SOFTDEVICE_PRESENT
    (void) sd_app_evt_wait();
#else
    __WFE();
#endif
}

int _fstorageWait(const uint32_t timeOutCnt) {
#if 0
    NRF_LOG_DEBUG("timeOutCnt=%u\n", timeOutCnt);
    const uint32_t startCnt   = app_timer_cnt_get();
    NRF_LOG_DEBUG("startCnt=%u\n", startCnt);
    uint32_t endCnt;
    uint32_t diffCnt;

    do {
        endCnt           = app_timer_cnt_get();
        diffCnt          = app_timer_cnt_diff_compute(endCnt, startCnt);
        if (diffCnt >= timeOutCnt) {
            NRF_LOG_WARNING("_fstorage wait timeout\n");
            return ERROR_FSTORAGE_WAIT_TIMEOUT;
        }

        if (_gFstorageEraseDone && _gFstorageWriteDone) {
            break;
        }
    } while (true);

    NRF_LOG_DEBUG("endCnt=%u\n", endCnt);
//#else
    //while (nrf_fstorage_is_busy(&_gFstorage))
    while (!_gFstorageEraseDone || !_gFstorageWriteDone) {
        NRF_LOG_DEBUG("pm e\n");
        power_manage();
        NRF_LOG_DEBUG("pm x\n");
    }
#endif
		return 0;
    
}

int _fstorageRead(const uint32_t addr, const uint32_t len, uint8_t* data) {
    if (!_gFstorageInitialized) {
        return ERROR_FSTORAGE_UNINITIALIZED;
    }   

    NRF_LOG_DEBUG("nrf_fstorage_read() e\n");
    const ret_code_t errCode = nrf_fstorage_read(&_gFstorage, addr, data, len);
    if (errCode != NRF_SUCCESS) {
        NRF_LOG_WARNING("nrf_fstorage_read() failed 0x%x\n", errCode);
        return ERROR_FSTORAGE_READ_FAIL;
    }
    NRF_LOG_DEBUG("nrf_fstorage_read() x\n");

    return 0;
}

int _fstorageWrite(const uint32_t addr, const uint32_t len, void const* pData) {
    if (!_gFstorageInitialized) {
        return ERROR_FSTORAGE_UNINITIALIZED;
    }

    if (!_gFstorageEraseDone) {
        return ERROR_FSTORAGE_ERASE_ON_GOING;
    }

    if (!_gFstorageWriteDone) {
        return ERROR_FSTORAGE_WRITE_ON_GOING;
    }

    _gFstorageWriteDone = false;
    _gFstorageState     = FSTORAGE_STATE_WRITING;
    const uint32_t wlen = _fstorageRoundUpU32(len);

    NRF_LOG_DEBUG("nrf_fstorage_write() e\n");
    const ret_code_t retCode = nrf_fstorage_write(&_gFstorage, addr, pData, wlen, NULL);
    if (retCode != NRF_SUCCESS) {
        NRF_LOG_WARNING("nrf_fstorage_write() failed 0x%x", retCode);
        return ERROR_FSTORAGE_WRITE_FAIL;
    }
    NRF_LOG_DEBUG("nrf_fstorage_write() x\n");
   
    _fstorageWait(FSTORAGE_WAIT_CNT);

    return 0;
}


static void _fstoragePrintInfo(void) {
    NRF_LOG_INFO("========| flash info |========");
    NRF_LOG_INFO("erase unit:   %d bytes", _gFstorage.p_flash_info->erase_unit);
    NRF_LOG_INFO("program unit: %d bytes", _gFstorage.p_flash_info->program_unit);
    NRF_LOG_INFO("end address: 0x%x",      _fstorageGetEndAddr());
    NRF_LOG_INFO("==============================");
}

int fstorageInit(void) {
    // initializing variables
    _gFstorageInitialized = false;
    _gFstorageEraseDone   = true;
    _gFstorageWriteDone   = true;
    _gFstoragePageDirty   = true;
    _gFstorageState = FSTORAGE_STATE_UNKNOWN;

    const ret_code_t errCode = nrf_fstorage_init(&_gFstorage, &nrf_fstorage_sd, NULL);
    if (errCode != NRF_SUCCESS) {
        NRF_LOG_WARNING("nrf_fstorage_init() failed 0x%x", errCode);
        return ERROR_FSTORAGE_INITIALIZE_FAIL;
    }

    _fstoragePrintInfo();
    _gFstorageInitialized = true;

    return 0;
}    

int fstorageErase(const uint32_t addr, const uint32_t pagesCnt) {
    if (!_gFstorageInitialized) {
        return ERROR_FSTORAGE_UNINITIALIZED;
    }

    if (!_gFstorageEraseDone) {
        return ERROR_FSTORAGE_ERASE_ON_GOING;
    }

    if (!_gFstorageWriteDone) {
        return ERROR_FSTORAGE_WRITE_ON_GOING;
    }    

    _gFstorageEraseDone = false;
    _gFstorageState     = FSTORAGE_STATE_ERASING;

    NRF_LOG_WARNING("nrf_fstorage_erase() e\n");
    const ret_code_t errCode = nrf_fstorage_erase(&_gFstorage, addr, pagesCnt, NULL);
    if (errCode != NRF_SUCCESS) { 
        NRF_LOG_WARNING("nrf_fstorage_erase() failed 0x%x", errCode);
        return ERROR_FSTORAGE_ERASE_FAIL;
    }
    NRF_LOG_WARNING("nrf_fstorage_erase() x\n");

    _fstorageWait(FSTORAGE_WAIT_CNT);
    
    return 0;
}

int fstorageReadRecord(const uint32_t addr, record_t* pRecord) {
    if (_fstorageRead(addr, RECORD_LEN, _gData) != 0) {
        return ERROR_FDS_READ_FAIL;
    }

    data2Record(_gData, pRecord);

    return 0;
}    

int fstorageUpdateRecord(const record_data_type_t type, const record_t* pRecord) {
    const uint32_t addr = fstorageGetStartAddr();
    NRF_LOG_DEBUG("type=%d, addr=0x%x\n", type, addr);
    const fstorage_state_t state = fstorageGetState();
    NRF_LOG_DEBUG("state=%d\n", state);
    switch (type) {
        case RECORD_DATA_TYPE_START:
        {  
            NRF_LOG_DEBUG("START E\n");
            if ((state == FSTORAGE_STATE_UNKNOWN)) {
                fstorageErase(addr, 1);    
            } else if (state == FSTORAGE_STATE_ERASED) {    
                record_t record;
                record.offsetStart = norFlashGetStartOffset();
                record.offsetEnd   = norFlashGetStartOffset();
                nrf_cal_get_datetime(&record.timeStart);
                nrf_cal_get_datetime(&record.timeEnd);
                recordPrintRecord("record", &record);
                fstorageWriteRecord(addr, &record);
            }    
            NRF_LOG_DEBUG("START x\n");
        }    
        break;

        case RECORD_DATA_TYPE_END:
        {
            NRF_LOG_DEBUG("END E\n");
            static record_t record;

            if ((state == FSTORAGE_STATE_UNKNOWN) || (state == FSTORAGE_STATE_WRITTEN)) {
                if (fstorageReadRecord(addr, &record) != 0) {
                    NRF_LOG_WARNING("fstorageReadRecord(0x%x) failed\n", addr);
                    return ERROR_FSTORAGE_READ_FAIL;
                }
                recordPrintRecord("record r", &record);
                fstorageErase(addr, 1);
            } else if (state == FSTORAGE_STATE_ERASED) {
                record.offsetEnd = pRecord->offsetEnd;
                record.timeEnd   = pRecord->timeEnd;
                recordPrintRecord("record w", &record);
                fstorageWriteRecord(addr, &record);
            }    
            NRF_LOG_DEBUG("END X");
        }   
        break;    
    }    

    return 0;
}

int fstorageWaitUpdateRecordFinish(void) {
    record_t record;
    record.offsetEnd   = norFlashGetWrittenOffset();
    nrf_cal_get_datetime(&record.timeEnd);
    if (fstorageUpdateRecord(RECORD_DATA_TYPE_END, &record) != 0) {
        NRF_LOG_WARNING("fstorageUpdateRecord(%d) failed\n", RECORD_DATA_TYPE_END);
        return ERROR_FSTORAGE_WRITE_FAIL;
    }    
    return 0;
}


int fstorageWriteRecord(const uint32_t addr, record_t* pRecord) {
    record2Data(pRecord, _gData);
    if (_fstorageWrite(addr, RECORD_LEN, _gData) != 0) {
        return ERROR_FSTORAGE_WRITE_FAIL;
    }

    return 0;
}

bool fstorageGetInitialized(void) {
    return _gFstorageInitialized;
}

bool fstorageGetEraseDone(void) {
    return _gFstorageEraseDone;
}

bool fstorageGetWriteDone(void) {
    return _gFstorageWriteDone;
}

bool fstorageGetPageDirty(void) {
    return _gFstoragePageDirty;
}

uint32_t fstorageGetStartAddr(void) {
    const uint32_t eraseUnit = _gFstorage.p_flash_info->erase_unit;
    const uint32_t endAddr = _fstorageGetEndAddr();
    const uint32_t bootloaderAddr = BOOTLOADER_ADDRESS;
    uint32_t startAddr;
    if (bootloaderAddr == 0xFFFFFFFF) {
        startAddr = endAddr - FSTORAGE_PAGE_NUM * eraseUnit + 1; 
    } else {
        startAddr = bootloaderAddr - FSTORAGE_PAGE_NUM * eraseUnit + 1;
    }

    NRF_LOG_DEBUG("endAddr=0x%x", endAddr);
    NRF_LOG_DEBUG("startAddr=0x%x", startAddr);
    return startAddr;
}

fstorage_state_t fstorageGetState(void) {
    return _gFstorageState;
}




