/*
 * Copyright (c) 2013, Robert Quattlebaum
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 *
 */

#ifndef CONTIKI_SETTINGS_H_
#define CONTIKI_SETTINGS_H_

/** @file   settings.h
 *  @brief  Settings Manager
 *  @author Robert Quattlebaum <darco@deepdarc.com>
 */

/** @addtogroup lib
 * @{ */

/** @defgroup settings_lib Settings Manager
 *
 *  The settings manager is an EEPROM-based key-value store. Keys
 *  are 16-bit integers and values may be up to 16,383 bytes long.
 *  It is intended to be used to store configuration-related information,
 *  like network settings, radio channels, etc.
 *
 *  ## Features ##
 *
 *   * Robust data format which requires no initialization.
 *   * Supports multiple values with the same key.
 *   * Data can be appended without erasing EEPROM.
 *   * Max size of settings data can be easily increased in the future,
 *     as long as it doesn't overlap with application data.
 *
 *  ## Data Format ##
 *
 *  The format was inspired by OLPC manufacturing data, as described here:
 *  <http://wiki.laptop.org/go/Manufacturing_data>
 *
 *  Since the beginning of EEPROM often contains application-specific
 *  information, the best place to store settings is at the end of
 *  EEPROM. Because we are starting at the end of EEPROM, it makes sense
 *  to grow the list of key-value pairs downward, toward the start of
 *  EEPROM.
 *
 *  Each key-value pair is stored in memory in the following format:
 *
 *  | Order    | Size     | Name       | Description                     |
 *  | -------- | -------- | ---------- | ------------------------------- |
 *  | 0        | 2        | key        |                                 |
 *  | -2       | 1        | size_check | One's-complement of next byte   |
 *  | -3       | 1 or 2   | size       | The size of the value, in bytes |
 *  | -4 or -5 | variable | value      |                                 |
 *
 *  The end of the key-value pairs is denoted by the first invalid entry.
 *  An invalid entry has any of the following attributes:
 *
 *   * The size_check byte doesn't match the one's compliment
 *     of the size byte (or size_low byte).
 *   * The key has a value of 0x0000.
 *
 * @{ */

#include <stdint.h>
#include <string.h>
#include "dev/eeprom.h"
#include "sys/cc.h"

/*****************************************************************************/
// MARK: - Types

typedef enum {
  SETTINGS_STATUS_OK = 0,
  SETTINGS_STATUS_FAILURE,
  SETTINGS_STATUS_INVALID_ARGUMENT,
  SETTINGS_STATUS_NOT_FOUND,
  SETTINGS_STATUS_OUT_OF_SPACE,
  SETTINGS_STATUS_VALUE_TOO_BIG,
  SETTINGS_STATUS_UNIMPLEMENTED,
} settings_status_t;

typedef uint16_t settings_key_t;

typedef uint16_t settings_length_t;

/*****************************************************************************/
// MARK: - Settings Keys

/** Two-character constant macro */
#define TCC(a,b)                   ((a)+(b)*256)

/* All-capital-letter constants are always contiki-defined. */
#define SETTINGS_KEY_EUI64         TCC('E','8') /**< EUI64 Address, 8 bytes */
#define SETTINGS_KEY_EUI48         TCC('E','6') /*!< MAC Address, 6 bytes */
#define SETTINGS_KEY_CHANNEL       TCC('C','H') /*!< Channel number, uint8_t */
#define SETTINGS_KEY_TXPOWER       TCC('T','P') /*!< Transmit power, uint8_t */
#define SETTINGS_KEY_PAN_ID        TCC('P','N') /*!< PAN ID, uint16_t */
#define SETTINGS_KEY_PAN_ADDR      TCC('P','A') /*!< PAN address, uint16_t */
#define SETTINGS_KEY_AES128KEY     TCC('S','K') /*!< AES128 key, 16 bytes */
#define SETTINGS_KEY_AES128ENABLED TCC('S','E') /*!< AES128 enabled, bool */
#define SETTINGS_KEY_HOSTNAME      TCC('H','N') /*!< Hostname, C-String */
#define SETTINGS_KEY_DOMAINNAME    TCC('D','N') /*!< Domainname, C-String */

/*****************************************************************************/
// MARK: - Experimental Settings Keys

#define SETTINGS_KEY_RDC_INDEX     TCC('R','D') /*!< RDC index, uint8_t */
#define SETTINGS_KEY_CHANNEL_MASK  TCC('C','M') /*!< Channel mask, uint16_t */

/*****************************************************************************/
// MARK: - Constants

/** Use this when you want to retrieve the last item */
#define SETTINGS_LAST_INDEX        0xFF
/** Returned when key is invalid. */
#define SETTINGS_INVALID_KEY       0xFFFF
/** Returned if no (further) element was found. */
#define SETTINGS_INVALID_ITER      EEPROM_NULL

#ifndef SETTINGS_CONF_SUPPORT_LARGE_VALUES
#define SETTINGS_CONF_SUPPORT_LARGE_VALUES  0
#endif

#if SETTINGS_CONF_SUPPORT_LARGE_VALUES
#define SETTINGS_MAX_VALUE_SIZE    0x3FFF        /* 16383 bytes */
#else
#define SETTINGS_MAX_VALUE_SIZE    0x7F        /* 127 bytes */
#endif

/*****************************************************************************/
// MARK: - Settings accessors

/** Fetches the value associated with the given key. */
extern settings_status_t settings_get(settings_key_t key, uint8_t index,
                                      uint8_t *value,
                                      settings_length_t * value_size);

/** Adds the given key-value pair to the end of the settings store. */
extern settings_status_t settings_add(settings_key_t key,
                                      const uint8_t *value,
                                      settings_length_t value_size);

/** Checks to see if the given key exists. */
extern uint8_t settings_check(settings_key_t key, uint8_t index);

/** Reinitializes all of the EEPROM used by settings. */
extern void settings_wipe(void);

/** Sets the value for the given key. If the key already exists in
 *  the settings store, then its value will be replaced.
 */
extern settings_status_t settings_set(settings_key_t key,
                                      const uint8_t *value,
                                      settings_length_t value_size);

/** Removes the given key (at the given index) from the settings store. */
extern settings_status_t settings_delete(settings_key_t key, uint8_t index);

/*****************************************************************************/
// MARK: - Settings traversal functions

typedef eeprom_addr_t settings_iter_t;

/** Will return \ref SETTINGS_INVALID_ITER if the settings store is empty. */
extern settings_iter_t settings_iter_begin();

/** Will return \ref SETTINGS_INVALID_ITER if at the end of settings list. */
extern settings_iter_t settings_iter_next(settings_iter_t iter);

extern uint8_t settings_iter_is_valid(settings_iter_t iter);

extern settings_key_t settings_iter_get_key(settings_iter_t iter);

extern settings_length_t settings_iter_get_value_length(settings_iter_t iter);

extern eeprom_addr_t settings_iter_get_value_addr(settings_iter_t iter);

extern settings_length_t settings_iter_get_value_bytes(settings_iter_t item,
                                                       void *bytes,
                                                       settings_length_t
                                                       max_length);

extern settings_status_t settings_iter_delete(settings_iter_t item);

/*****************************************************************************/
// MARK: - inline convenience functions

/* Unfortunately, some platforms don't properly drop unreferenced functions,
 * so on these broken platforms we can save a significant amount
 * of space by skipping the definition of the convenience functions.
 */
#if !SETTINGS_CONF_SKIP_CONVENIENCE_FUNCS

static CC_INLINE const char *
settings_get_cstr(settings_key_t key, uint8_t index, char *c_str,
                  settings_length_t c_str_size)
{
  /* Save room for the zero termination. */
  c_str_size--;

  if(settings_get(key, index, (uint8_t *)c_str, &c_str_size) == SETTINGS_STATUS_OK) {
    /* Zero terminate. */
    c_str[c_str_size] = 0;
  } else {
    c_str = NULL;
  }
  return c_str;
}

static CC_INLINE settings_status_t
settings_set_cstr(settings_key_t key, const char* c_str)
{
  return settings_set(key, (const uint8_t *)c_str, strlen(c_str));
}

static CC_INLINE settings_status_t
settings_add_cstr(settings_key_t key, const char* c_str)
{
  return settings_add(key, (const uint8_t *)c_str, strlen(c_str));
}

static CC_INLINE uint8_t
settings_get_bool_with_default(settings_key_t key, uint8_t index,
                               uint8_t default_value)
{
  uint8_t ret = default_value;
  settings_length_t sizeof_uint8 = sizeof(uint8_t);

  settings_get(key, index, (uint8_t *)&ret, &sizeof_uint8);
  return !!ret;
}

static CC_INLINE uint8_t
settings_get_uint8(settings_key_t key, uint8_t index)
{
  uint8_t ret = 0;
  settings_length_t sizeof_uint8 = sizeof(uint8_t);

  settings_get(key, index, (uint8_t *)&ret, &sizeof_uint8);
  return ret;
}

static CC_INLINE settings_status_t
settings_add_uint8(settings_key_t key, uint8_t value)
{
  return settings_add(key, (const uint8_t *)&value, sizeof(uint8_t));
}

static CC_INLINE settings_status_t
settings_set_uint8(settings_key_t key, uint8_t value)
{
  return settings_set(key, (const uint8_t *)&value, sizeof(uint8_t));
}

static CC_INLINE uint16_t
settings_get_uint16(settings_key_t key, uint8_t index)
{
  uint16_t ret = 0;
  settings_length_t sizeof_uint16 = sizeof(uint16_t);

  settings_get(key, index, (uint8_t *)&ret, &sizeof_uint16);
  return ret;
}

static CC_INLINE settings_status_t
settings_add_uint16(settings_key_t key, uint16_t value)
{
  return settings_add(key, (const uint8_t *)&value, sizeof(uint16_t));
}

static CC_INLINE settings_status_t
settings_set_uint16(settings_key_t key, uint16_t value)
{
  return settings_set(key, (const uint8_t *)&value, sizeof(uint16_t));
}

static CC_INLINE uint32_t
settings_get_uint32(settings_key_t key, uint8_t index)
{
  uint32_t ret = 0;
  settings_length_t sizeof_uint32 = sizeof(uint32_t);

  settings_get(key, index, (uint8_t *)&ret, &sizeof_uint32);
  return ret;
}

static CC_INLINE settings_status_t
settings_add_uint32(settings_key_t key, uint32_t value)
{
  return settings_add(key, (const uint8_t *)&value, sizeof(uint32_t));
}

static CC_INLINE settings_status_t
settings_set_uint32(settings_key_t key, uint32_t value)
{
  return settings_set(key, (const uint8_t *)&value, sizeof(uint32_t));
}

#if __int64_t_defined
static CC_INLINE uint64_t
settings_get_uint64(settings_key_t key, uint8_t index)
{
  uint64_t ret = 0;
  settings_length_t sizeof_uint64 = sizeof(uint64_t);

  settings_get(key, index, (uint8_t *)&ret, &sizeof_uint64);
  return ret;
}

static CC_INLINE settings_status_t
settings_add_uint64(settings_key_t key, uint64_t value)
{
  return settings_add(key, (const uint8_t *)&value, sizeof(uint64_t));
}

static CC_INLINE settings_status_t
settings_set_uint64(settings_key_t key, uint64_t value)
{
  return settings_set(key, (const uint8_t *)&value, sizeof(uint64_t));
}
#endif /* __int64_t_defined */

#endif /* !SETTINGS_CONF_SKIP_CONVENIENCE_FUNCS */

#endif /* !defined(CONTIKI_SETTINGS_H_) */

/** @} */
/** @} */
