/*
 * Copyright (c) 2018 ARM Limited. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 * Licensed under the Apache License, Version 2.0 (the License); you may
 * not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef MBED_NVSTORE_H
#define MBED_NVSTORE_H

// These addresses need to be configured according to board (in mbed_lib.json)
#ifndef DEVICE_FLASH
#undef NVSTORE_ENABLED
#define NVSTORE_ENABLED 0
#endif

#if (NVSTORE_ENABLED) || defined(DOXYGEN_ONLY)
#include <stdint.h>
#include <stdio.h>
#include "platform/NonCopyable.h"
#include "PlatformMutex.h"
#include "FlashIAP.h"

typedef enum {
    NVSTORE_SUCCESS                =  0,
    NVSTORE_READ_ERROR             = -1,
    NVSTORE_WRITE_ERROR            = -2,
    NVSTORE_NOT_FOUND              = -3,
    NVSTORE_DATA_CORRUPT           = -4,
    NVSTORE_BAD_VALUE              = -5,
    NVSTORE_BUFF_TOO_SMALL         = -6,
    NVSTORE_FLASH_AREA_TOO_SMALL   = -7,
    NVSTORE_OS_ERROR               = -8,
    NVSTORE_ALREADY_EXISTS         = -9,
    NVSTORE_NO_FREE_KEY            = -10,
} nvstore_status_e;

typedef enum {
    NVSTORE_FIRST_PREDEFINED_KEY        = 0,

    // All predefined keys used for internal features should be defined here

    NVSTORE_DEVICEKEY_KEY               = 4,

    NVSTORE_LAST_PREDEFINED_KEY         = 15,
    NVSTORE_NUM_PREDEFINED_KEYS
} nvstore_predefined_keys_e;

typedef enum {
    NVSTORE_UNSPECIFIED_OWNER           = 0,
    // All owners (by features) should be specified here.
    NVSTORE_MAX_OWNERS                  = 16
} nvstore_owner_e;

#ifndef NVSTORE_MAX_KEYS
#define NVSTORE_MAX_KEYS ((uint16_t)NVSTORE_NUM_PREDEFINED_KEYS)
#endif

// defines 2 areas - active and nonactive, not configurable
#define NVSTORE_NUM_AREAS        2

/** NVStore class
 *
 *  Class for storing data by keys in the internal flash
 */

class NVStore : private mbed::NonCopyable<NVStore> {
public:

    /**
    * @brief As a singleton, return the single instance of the class.
    *        Reason for this class being a singleton is the following:
    *        - Ease the use for users of this class not having to coordinate instantiations.
    *        - Lazy instantiation of internal data (which we can't achieve with simple static classes).
    *
    * @returns Singleton instance reference.
    */
    static NVStore& get_instance()
    {
        // Use this implementation of singleton (Meyer's) rather than the one that allocates
        // the instance on the heap because it ensures destruction at program end (preventing warnings
        // from memory checking tools).
        static NVStore instance;
        return instance;
    }

    virtual ~NVStore();

    /**
     * @brief Returns number of keys.
     *
     * @returns Number of keys.
     */
    uint16_t get_max_keys() const;

    /**
     * @brief Set number of keys.
     *
     * @returns None.
     */
    void set_max_keys(uint16_t num_keys);

    /**
     * @brief Return maximal possible number of keys (in this flash configuration).
     *
     * @returns Max possible number of keys.
     */
    uint16_t get_max_possible_keys();

    /**
     * @brief Returns one item of data programmed on Flash, given key.
     *
     * @param[in]  key                  Key of stored item.
     * @param[in]  buf_size             Length of input buffer in bytes.
     * @param[in]  buf                  Buffer to store data on.
     *
     * @param[out] actual_size          Actual size of returned data.
     *
     * @returns NVSTORE_SUCCESS           Value was found on Flash.
     *          NVSTORE_NOT_FOUND         Value was not found on Flash.
     *          NVSTORE_READ_ERROR        Physical error reading data.
     *          NVSTORE_DATA_CORRUPT      Data on Flash is corrupt.
     *          NVSTORE_BAD_VALUE         Bad value in any of the parameters.
     *          NVSTORE_BUFF_TOO_SMALL    Not enough memory in user buffer.
     */
    int get(uint16_t key, uint16_t buf_size, void *buf, uint16_t &actual_size);

    /**
     * @brief Returns size of the data programmed on Flash, given key.
     *
     * @param[in]  key                  Key of stored item.
     * @param[out] actual_size          Actual size of item
     *
     * @returns NVSTORE_SUCCESS           Value was found on Flash.
     *          NVSTORE_NOT_FOUND         Value was not found on Flash.
     *          NVSTORE_READ_ERROR        Physical error reading data.
     *          NVSTORE_DATA_CORRUPT      Data on Flash is corrupt.
     *          NVSTORE_BAD_VALUE         Bad value in any of the parameters.
     */
    int get_item_size(uint16_t key, uint16_t &actual_size);


    /**
     * @brief Programs one item of data on Flash, given key.
     *
     * @param[in]  key                  Key of stored item.
     * @param[in]  buf_size             Item size in bytes.
     * @param[in]  buf                  Buffer containing data.
     *
     * @returns NVSTORE_SUCCESS           Value was successfully written on Flash.
     *          NVSTORE_WRITE_ERROR       Physical error writing data.
     *          NVSTORE_BAD_VALUE         Bad value in any of the parameters.
     *          NVSTORE_FLASH_AREA_TOO_SMALL
     *                                    Not enough space in Flash area.
     *          NVSTORE_ALREADY_EXISTS    Item set with write once API already exists.
     *
     */
    int set(uint16_t key, uint16_t buf_size, const void *buf);

    /**
     * @brief Allocate a free key (to be used later in a set operation).
     *
     * @param[out] key                  Returned key of stored item.
     * @param[in]  owner                Owner of allocated key.
     *
     * @returns NVSTORE_SUCCESS           Key was successfully allocated.
     *          NVSTORE_NO_FREE_KEY       Couldn't allocate a key for this call.
     *
     */
    int allocate_key(uint16_t &key, uint8_t owner = NVSTORE_UNSPECIFIED_OWNER);

    /**
     * @brief Free all allocated keys that belong to a specific owner.
     *
     * @param[in]  owner                Owner.
     *
     * @returns NVSTORE_SUCCESS           Value was successfully written on Flash.
     *          NVSTORE_WRITE_ERROR       Physical error writing data.
     *          NVSTORE_BAD_VALUE         Bad value in any of the parameters.
     *
     */
    int free_all_keys_by_owner(uint8_t owner);

    /**
     * @brief Programs one item of data on Flash, given key, allowing no consequent sets to this key.
     *
     * @param[in]  key                  Key of stored item.
     * @param[in]  buf_size             Item size in bytes.
     * @param[in]  buf                  Buffer containing data.
     *
     * @returns NVSTORE_SUCCESS           Value was successfully written on Flash.
     *          NVSTORE_WRITE_ERROR       Physical error writing data.
     *          NVSTORE_BAD_VALUE         Bad value in any of the parameters.
     *          NVSTORE_FLASH_AREA_TOO_SMALL
     *                                    Not enough space in Flash area.
     *          NVSTORE_ALREADY_EXISTS    Item set with write once API already exists.
     *
     */
    int set_once(uint16_t key, uint16_t buf_size, const void *buf);


    /**
     * @brief Remove an item from flash.
     *
     * @param[in]  key                  Key of stored item.
     *
     * @returns NVSTORE_SUCCESS           Value was successfully written on Flash.
     *          NVSTORE_WRITE_ERROR       Physical error writing data.
     *          NVSTORE_BAD_VALUE         Bad value in any of the parameters.
     *          NVSTORE_FLASH_AREA_TOO_SMALL
     *                                    Not enough space in Flash area.
     *
     */
    int remove(uint16_t key);

    /**
     * @brief Initializes NVStore component.
     *
     * @returns NVSTORE_SUCCESS       Initialization completed successfully.
     *          NVSTORE_READ_ERROR    Physical error reading data.
     *          NVSTORE_WRITE_ERROR   Physical error writing data (on recovery).
     *          NVSTORE_FLASH_AREA_TOO_SMALL
     *                                Not enough space in Flash area.
     */
    int init();

    /**
     * @brief Deinitializes NVStore component.
     *        Warning: This function is not thread safe and should not be called
     *        concurrently with other NVStore functions.
     *
     * @returns NVSTORE_SUCCESS       Deinitialization completed successfully.
     */
    int deinit();

    /**
     * @brief Reset Flash NVStore areas.
     *        Warning: This function is not thread safe and should not be called
     *        concurrently with other NVStore functions.
     *
     * @returns NVSTORE_SUCCESS       Reset completed successfully.
     *          NVSTORE_READ_ERROR    Physical error reading data.
     *          NVSTORE_WRITE_ERROR   Physical error writing data.
     */
    int reset();

    /**
     * @brief Return NVStore size (area size).
     *
     * @returns NVStore size.
     */
    size_t size();

    /**
     * @brief Return address and size of an NVStore area.
     *
     * @param[in]  area                   Area.
     * @param[out] address                Area address.
     * @param[out] size                   Area size (bytes).
     *
     * @returns NVSTORE_SUCCESS           Success.
     *          NVSTORE_BAD_VALUE         Bad area parameter.
     */
    int get_area_params(uint8_t area, uint32_t &address, size_t &size);


private:
    typedef struct {
        uint32_t address;
        size_t   size;
    } nvstore_area_data_t;

    int _init_done;
    uint32_t _init_attempts;
    uint8_t _active_area;
    uint16_t _max_keys;
    uint16_t _active_area_version;
    uint32_t _free_space_offset;
    size_t _size;
    PlatformMutex *_mutex;
    uint32_t *_offset_by_key;
    nvstore_area_data_t _flash_area_params[NVSTORE_NUM_AREAS];
    static nvstore_area_data_t initial_area_params[NVSTORE_NUM_AREAS];
    mbed::FlashIAP *_flash;
    uint32_t _min_prog_size;
    uint8_t *_page_buf;

    // Private constructor, as class is a singleton
    NVStore();

    /**
     * @brief Read a block from an area.
     *
     * @param[in]  area                   Area.
     * @param[in]  offset                 Offset in area.
     * @param[in]  size                   Number of bytes to read.
     * @param[in]  buf                    Output buffer.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int flash_read_area(uint8_t area, uint32_t offset, uint32_t size, void *buf);

    /**
     * @brief Write a block to an area.
     *
     * @param[in]  area                   Area.
     * @param[in]  offset                 Offset in area.
     * @param[in]  size                   Number of bytes to write.
     * @param[in]  buf                    Input buffer.
     *
     * @returns 0 for success, non-zero for failure.
     */
    int flash_write_area(uint8_t area, uint32_t offset, uint32_t size, const void *buf);

    /**
     * @brief Erase an area.
     *
     * @param[in]  area                   Area.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int flash_erase_area(uint8_t area);

    /**
     * @brief Calculate addresses and sizes of areas (in case no user configuration is given),
     *        or validate user configuration (if given).
     */
    void calc_validate_area_params();

    /**
     * @brief Calculate empty (unprogrammed) continuous space at the end of the area.
     *
     * @param[in]  area                   Area.
     * @param[out] offset                 Offset of empty space.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int calc_empty_space(uint8_t area, uint32_t &offset);

    /**
     * @brief Read an NVStore record from a given location.
     *
     * @param[in]  area                   Area.
     * @param[in]  offset                 Offset of record in area.
     * @param[in]  buf_size               Buffer size (bytes).
     * @param[in]  buf                    Output Buffer.
     * @param[out] actual_size            Actual data size (bytes).
     * @param[in]  validate_only          Just validate (without reading to buffer).
     * @param[out] valid                  Is the record valid.
     * @param[out] key                    Record key.
     * @param[out] flags                  Record flags.
     * @param[out] owner                  Owner.
     * @param[out] next_offset            Offset of next record.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int read_record(uint8_t area, uint32_t offset, uint16_t buf_size, void *buf,
                    uint16_t &actual_size, int validate_only, int &valid,
                    uint16_t &key, uint16_t &flags, uint8_t &owner, uint32_t &next_offset);

    /**
     * @brief Write an NVStore record from a given location.
     *
     * @param[in]  area                   Area.
     * @param[in]  offset                 Offset of record in area.
     * @param[in]  key                    Record key.
     * @param[in]  flags                  Record flags.
     * @param[in]  owner                  Owner.
     * @param[in]  data_size              Data size (bytes).
     * @param[in]  data_buf               Data buffer.
     * @param[out] next_offset            Offset of next record.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int write_record(uint8_t area, uint32_t offset, uint16_t key, uint16_t flags, uint8_t owner,
                     uint32_t data_size, const void *data_buf, uint32_t &next_offset);

    /**
     * @brief Write a master record of a given area.
     *
     * @param[in]  area                   Area.
     * @param[in]  version                Area version.
     * @param[out] next_offset            Offset of next record.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int write_master_record(uint8_t area, uint16_t version, uint32_t &next_offset);

    /**
     * @brief Copy a record from one area to the other one.
     *
     * @param[in]  from_area              Area to copy record from.
     * @param[in]  from_offset            Offset in source area.
     * @param[in]  to_offset              Offset in destination area.
     * @param[out] next_offset            Offset of next record.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int copy_record(uint8_t from_area, uint32_t from_offset, uint32_t to_offset,
                    uint32_t &next_offset);

    /**
     * @brief Garbage collection (compact all records from active area to nonactive ones).
     *        All parameters belong to a record that needs to be written before the process.
     *
     * @param[in]  key                    Record key.
     * @param[in]  flags                  Record flags.
     * @param[in]  owner                  Owner.
     * @param[in]  buf_size               Data size (bytes).
     * @param[in]  buf                    Data buffer.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int garbage_collection(uint16_t key, uint16_t flags, uint8_t owner, uint16_t buf_size, const void *buf);

    /**
     * @brief Actual logics of get API (covers also get size API).
     *
     * @param[in]  key                    key.
     * @param[in]  buf_size               Buffer size (bytes).
     * @param[in]  buf                    Output Buffer.
     * @param[out] actual_size            Actual data size (bytes).
     * @param[in]  validate_only          Just validate (without reading to buffer).
     *
     * @returns 0 for success, nonzero for failure.
     */
    int do_get(uint16_t key, uint16_t buf_size, void *buf, uint16_t &actual_size,
               int validate_only);

    /**
     * @brief Actual logics of set API (covers also set_once and remove APIs).
     *
     * @param[in]  key                    key.
     * @param[in]  buf_size               Buffer size (bytes).
     * @param[in]  buf                    Input Buffer.
     * @param[in]  flags                  Record flags.
     *
     * @returns 0 for success, nonzero for failure.
     */
    int do_set(uint16_t key, uint16_t buf_size, const void *buf, uint16_t flags);

};
/** @}*/

#endif // NVSTORE_ENABLED

#endif
