/****************************************************************************
 * apps/system/settings/settings.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you 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.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <nuttx/crc32.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <nuttx/config.h>

#include "storage.h"
#include "system/settings.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#ifdef CONFIG_SYSTEM_SETTINGS_CACHED_SAVES
#  define TIMER_SIGNAL SIGRTMIN
#endif

#ifndef CONFIG_SYSTEM_SETTINGS_CACHE_TIME_MS
#  define CONFIG_SYSTEM_SETTINGS_CACHE_TIME_MS 100
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

static int      sanity_check(FAR char *str);
static uint32_t hash_calc(void);
static int      get_setting(FAR char *key, FAR setting_t **setting);
static size_t   get_string(FAR setting_t *setting, FAR char *buffer,
                         size_t size);
static int      set_string(FAR setting_t *setting, FAR char *str);
static int      get_int(FAR setting_t *setting, FAR int *i);
static int      set_int(FAR setting_t *setting, int i);
static int      get_bool(FAR setting_t *setting, FAR int *i);
static int      set_bool(FAR setting_t *setting, int i);
static int      get_float(FAR setting_t *setting, FAR double *f);
static int      set_float(FAR setting_t *setting, FAR double f);
static int      get_ip(FAR setting_t *setting, FAR struct in_addr *ip);
static int      set_ip(FAR setting_t *setting, FAR struct in_addr *ip);
static int      load(void);
static void     save(void);
static void     signotify(void);
static void     dump_cache(union sigval ptr);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct
{
  pthread_mutex_t   mtx;
  uint32_t          hash;
  bool              wrpend;
  bool              initialized;
  storage_t         store[CONFIG_SYSTEM_SETTINGS_MAX_STORAGES];
  struct notify_s   notify[CONFIG_SYSTEM_SETTINGS_MAX_SIGNALS];
#if defined(CONFIG_SYSTEM_SETTINGS_CACHED_SAVES)
  struct sigevent   sev;
  struct itimerspec trigger;
  timer_t           timerid;
#endif
} g_settings;

setting_t map[CONFIG_SYSTEM_SETTINGS_MAP_SIZE];

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: hash_calc
 *
 * Description:
 *    Gets a setting for a string value
 *
 * Input Parameters:
 *    none
 * Returned Value:
 *   crc32 hash of all the settings
 *
 ****************************************************************************/

static uint32_t hash_calc(void)
{
  return crc32((FAR uint8_t *)map, sizeof(map));
}

/****************************************************************************
 * Name: get_setting
 *
 * Description:
 *    Gets a setting for a given key
 *
 * Input Parameters:
 *    key        - key of the required setting
 *    setting    - pointer to pointer for the setting
 *
 * Returned Value:
 *   The value of the setting for the given key
 *
 ****************************************************************************/

static int get_setting(FAR char *key, FAR setting_t **setting)
{
  int i;
  int ret = OK;

  assert(*setting == NULL);

  for (i = 0; i < CONFIG_SYSTEM_SETTINGS_MAP_SIZE; i++)
    {
      if (map[i].type == SETTING_EMPTY)
        {
          ret = -ENOENT;
          goto exit;
        }

      if (strcmp(map[i].key, key) == 0)
        {
          *setting = &map[i];
          goto exit;
        }
    }

  *setting = NULL;
  ret = -ENOENT;

exit:
  return ret;
}

/****************************************************************************
 * Name: get_string
 *
 * Description:
 *    Gets a setting for a string value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    str            - pointer to return the string value
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static size_t get_string(FAR setting_t *setting, FAR char *buffer,
                         size_t size)
{
  assert(setting);
  if ((setting->type != SETTING_STRING) &&
      (setting->type != SETTING_IP_ADDR))
    {
      return -EACCES;
    }

  if (setting->type == SETTING_STRING)
    {
      FAR const char *s = setting->val.s;
      size_t len = strlen(s);

      assert(len < size);
      if (len >= size)
        {
          return 0;
        }

      strncpy(buffer, s, size);
      buffer[size - 1] = '\0';

      return len;
    }
  else if (setting->type == SETTING_IP_ADDR)
    {
      assert(INET_ADDRSTRLEN < size);

      inet_ntop(AF_INET, &setting->val.ip, buffer, size);
      buffer[size - 1] = '\0';

      return strlen(buffer);
    }

  return 0;
}

/****************************************************************************
 * Name: set_string
 *
 * Description:
 *    Creates a setting for a string value setting->type != set_
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    str            - the string value
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int set_string(FAR setting_t *setting, FAR char *str)
{
  assert(setting);

  if ((setting->type != SETTING_STRING) &&
      (setting->type != SETTING_IP_ADDR))
    {
      return -EACCES;
    }

  ASSERT(strlen(str) < CONFIG_SYSTEM_SETTINGS_VALUE_SIZE);
  if (strlen(str) >= CONFIG_SYSTEM_SETTINGS_VALUE_SIZE)
    {
      return -EINVAL;
    }

  if (strlen(str) && (sanity_check(str) < 0))
    {
      return -EINVAL;
    }

  setting->type = SETTING_STRING;
  strncpy(setting->val.s, str, CONFIG_SYSTEM_SETTINGS_VALUE_SIZE);
  setting->val.s[CONFIG_SYSTEM_SETTINGS_VALUE_SIZE - 1] = '\0';

  return OK;
}

/****************************************************************************
 * Name: get_int
 *
 * Description:
 *    Gets a setting for an integer value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    i              - pointer to return the integer value
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int get_int(FAR setting_t *setting, FAR int *i)
{
  assert(setting);
  if ((setting->type != SETTING_INT) &&
      (setting->type != SETTING_BOOL) &&
      (setting->type != SETTING_FLOAT))
    {
      return -EACCES;
    }

  if (setting->type == SETTING_INT)
    {
      *i = setting->val.i;
    }
  else if (setting->type == SETTING_BOOL)
    {
      *i = !!setting->val.i;
    }
  else if (setting->type == SETTING_FLOAT)
    {
      *i = (int)setting->val.f;
    }
  else
    {
      return -EINVAL;
    }

  return OK;
}

/****************************************************************************
 * Name: set_int
 *
 * Description:
 *    Creates a setting for an integer value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    i              - the integer value
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int set_int(FAR setting_t *setting, int i)
{
  assert(setting);
  if ((setting->type != SETTING_INT)  &&
      (setting->type != SETTING_BOOL) &&
      (setting->type != SETTING_FLOAT))
    {
      return -EACCES;
    }

  setting->type = SETTING_INT;
  setting->val.i = i;

  return OK;
}

/****************************************************************************
 * Name: get_bool
 *
 * Description:
 *    Gets a setting for a boolean value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    f              - pointer to return the boolean value
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int get_bool(FAR setting_t *setting, FAR int *i)
{
  assert(setting);
  if ((setting->type != SETTING_BOOL) &&
      (setting->type != SETTING_INT))
    {
      return -EACCES;
    }

  if ((setting->type == SETTING_INT) || (setting->type == SETTING_BOOL))
    {
      *i = !!setting->val.i;
    }
  else
    {
      return -EINVAL;
    }

  return OK;
}

/****************************************************************************
 * Name: set_bool
 *
 * Description:
 *    Creates a setting for a boolean value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    i              - the boolean value
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int set_bool(FAR setting_t *setting, int i)
{
  assert(setting);
  if ((setting->type != SETTING_BOOL) && (setting->type != SETTING_INT))
    {
      return -EACCES;
    }

  setting->type = SETTING_BOOL;
  setting->val.i = !!i;

  return OK;
}

/****************************************************************************
 * Name: get_float
 *
 * Description:
 *    Gets a setting for a float value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    f              - pointer to return the floating point value
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int get_float(FAR setting_t *setting, FAR double *f)
{
  assert(setting);
  if ((setting->type != SETTING_FLOAT) &&
      (setting->type != SETTING_INT))
    {
      return -EACCES;
    }

  if (setting->type == SETTING_FLOAT)
    {
      *f = setting->val.f;
    }
  else if (setting->type == SETTING_INT)
    {
      *f = (double)setting->val.i;
    }
  else
    {
      return -EINVAL;
    }

  return OK;
}

/****************************************************************************
 * Name: set_float
 *
 * Description:
 *    Creates a setting for a float value
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    f              - the floating point value
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int set_float(FAR setting_t *setting, double f)
{
  assert(setting);
  if ((setting->type != SETTING_FLOAT) &&
      (setting->type != SETTING_INT))
    {
      return -EACCES;
    }

  setting->type = SETTING_FLOAT;
  setting->val.f = f;

  return OK;
}

/****************************************************************************
 * Name: get_ip
 *
 * Description:
 *    Creates a setting for an IP address
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    ip             - pointer to return the IP address
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int get_ip(FAR setting_t *setting, FAR struct in_addr *ip)
{
  int ret;
  assert(setting);
  if ((setting->type != SETTING_IP_ADDR) &&
      (setting->type != SETTING_STRING))
    {
      return -EACCES;
    }

  if (setting->type == SETTING_IP_ADDR)
    {
      memcpy(ip, &setting->val.ip, sizeof(struct in_addr));
      ret = OK;
    }
  else if (setting->type == SETTING_STRING)
    {
      ret = inet_pton(AF_INET, setting->val.s, ip);
    }
  else
    {
      ret = -EINVAL;
    }

  return ret;
}

/****************************************************************************
 * Name: set_ip
 *
 * Description:
 *    Creates a setting for an IP address
 *
 * Input Parameters:
 *    setting        - pointer to the setting type
 *    ip             - IP address (in network byte order)
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int set_ip(FAR setting_t *setting, FAR struct in_addr *ip)
{
  assert(setting);
  if ((setting->type != SETTING_IP_ADDR) &&
      (setting->type != SETTING_STRING))
    {
      return -EACCES;
    }

  setting->type = SETTING_IP_ADDR;
  memcpy(&setting->val.ip, ip, sizeof(struct in_addr));

  return OK;
}

/****************************************************************************
 * Name: load
 *
 * Description:
 *    Loads all values
 *
 * Input Parameters:
 *    none
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int load(void)
{
  int i;
  int ret;
  int loadfailed = 0;

  for (i = 0; i < CONFIG_SYSTEM_SETTINGS_MAX_STORAGES; i++)
    {
      if ((g_settings.store[i].file[0] != '\0') &&
           g_settings.store[i].load_fn)
        {
          ret = g_settings.store[i].load_fn(g_settings.store[i].file);
          if (ret < 0)
            {
              loadfailed++;
            }
        }
    }

  if (loadfailed >= CONFIG_SYSTEM_SETTINGS_MAX_STORAGES)
    {
      /* ALL storages failed to load. We have a problem. */

      return -ENODATA;
    }
  else
    {
      return OK;
    }
}

/****************************************************************************
 * Name: save
 *
 * Description:
 *    Saves cached values, either immediately or on a timer
 *
 * Input Parameters:
 *    none
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void save(void)
{
  g_settings.wrpend = true;

#ifdef CONFIG_SYSTEM_SETTINGS_CACHED_SAVES
  ret = timer_settime(g_settings.timerid, 0, &g_settings.trigger, NULL);
#else
  union sigval value =
  {
    .sival_ptr = &g_settings.wrpend,
  };

  dump_cache(value);
#endif
}

/****************************************************************************
 * Name: signotify
 *
 * Description:
 *    Notify anything waiting for a signal
 *
 * Input Parameters:
 *    none
 *
 * Returned Value:
 *    none
 *
 ****************************************************************************/

static void signotify(void)
{
  int i;

  for (i = 0; i < CONFIG_SYSTEM_SETTINGS_MAX_SIGNALS; i++)
    {
      if (g_settings.notify[i].pid == 0)
        {
          break;
        }

      kill(g_settings.notify[i].pid, g_settings.notify[i].signo);
    }
}

/****************************************************************************
 * Name: dump_cache
 *
 * Description:
 *    Writes out the cached data to the appropriate storage
 *
 * Input Parameters:
 *    value            - parameter passed if called from a timer signal
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void dump_cache(union sigval ptr)
{
  int ret = OK;
  FAR bool *wrpend = (bool *)ptr.sival_ptr;

  int i;

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      assert(0);
    }

  for (i = 0; i < CONFIG_SYSTEM_SETTINGS_MAX_STORAGES; i++)
    {
      if ((g_settings.store[i].file[0] != '\0') &&
           g_settings.store[i].save_fn)
        {
          ret = g_settings.store[i].save_fn(g_settings.store[i].file);
#if 0
          if (ret < 0)
            {
              /* What to do? We can't return anything from a void function.
               *
               * MIGHT BE A FUTURE REVISIT NEEDED
               */
            }
#endif
        }
    }

  *wrpend = false;

  pthread_mutex_unlock(&g_settings.mtx);
}

/****************************************************************************
 * Name: sanity_check
 *
 * Description:
 *    Checks that the string does not contain "illegal" characters
 *
 * Input Parameters:
 *    str              - the string to check
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

static int sanity_check(FAR char *str)
{
#ifdef CONFIG_DEBUG_ASSERTIONS
  assert(strchr(str, '=')  == NULL);
  assert(strchr(str, ';')  == NULL);
  assert(strchr(str, '\n') == NULL);
  assert(strchr(str, '\r') == NULL);
#else
  if ((strchr(str, '=')  != NULL) || (strchr(str, ';')  != NULL) ||
      (strchr(str, '\n') != NULL) || (strchr(str, '\r') != NULL))
    {
      return -EINVAL;
    }

#endif

  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: settings_init
 *
 * Description:
 *   Initializes the settings storage.
 *
 * Input Parameters:  none
 *
 * Returned Value:    none
 *
 ****************************************************************************/

void settings_init(void)
{
  pthread_mutexattr_t attr;

  pthread_mutexattr_init(&attr);
  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
  pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
  pthread_mutex_init(&g_settings.mtx, &attr);

  memset(map, 0, sizeof(map));
  memset(g_settings.store, 0, sizeof(g_settings.store));
  memset(g_settings.notify, 0, sizeof(g_settings.notify));

#if defined(CONFIG_SYSTEM_SETTINGS_CACHED_SAVES)
  memset(&g_settings.sev, 0, sizeof(struct sigevent));
  g_settings.sev.sigev_notify          = SIGEV_THREAD;
  g_settings.sev.sigev_signo           = TIMER_SIGNAL;
  g_settings.sev.sigev_value.sival_ptr = &g_settings.wrpend;
  g_settings.sev.sigev_notify_function = dump_cache;

  memset(&g_settings.trigger, 0, sizeof(struct itimerspec));
  g_settings.trigger.it_value.tv_sec   =
                               CONFIG_SYSTEM_SETTINGS_CACHE_TIME_MS / 1000;
  g_settings.trigger.it_value.tv_nsec  =
                              (CONFIG_SYSTEM_SETTINGS_CACHE_TIME_MS % 1000) *
                               1000 * 1000;

  timer_create(CLOCK_REALTIME, &g_settings.sev, &g_settings.timerid);
#endif
  g_settings.initialized = true;
  g_settings.hash = 0;
  g_settings.wrpend = false;
}

/****************************************************************************
 * Name: settings_setstorage
 *
 * Description:
 *    Sets a file to be used as a settings storage.
 *    Except from the first file, if loading the file causes any changes
 *    to the settings, then the new map will be dumped to all files
 *    (effectively it syncs all storages).
 *
 * Input Parameters:
 *    file             - the filename of the storage to use
 *    type             - the type of the storage (BINARY or TEXT)
 *
 * Returned Value:
 *   Success or negated failure code
 *
 ****************************************************************************/

int settings_setstorage(FAR char *file, enum storage_type_e type)
{
  FAR storage_t *storage = NULL;
  int ret = OK;
  int idx = 0;
  uint32_t h;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  while ((idx < CONFIG_SYSTEM_SETTINGS_MAX_STORAGES) &&
         (g_settings.store[idx].file[0] != '\0'))
    {
      idx++;
    }

  assert(idx < CONFIG_SYSTEM_SETTINGS_MAX_STORAGES);
  if (idx >= CONFIG_SYSTEM_SETTINGS_MAX_STORAGES)
    {
      ret = -ENOSPC;
      goto errout;
    }

  assert(strlen(file) < CONFIG_SYSTEM_SETTINGS_MAX_FILENAME);
  if (strlen(file) >= CONFIG_SYSTEM_SETTINGS_MAX_FILENAME)
    {
      ret = -EINVAL;
      goto errout;
    }

  storage = &g_settings.store[idx];
  strncpy(storage->file, file, sizeof(storage->file));
  storage->file[sizeof(storage->file) - 1] = '\0';

  switch (type)
  {
    case STORAGE_BINARY:
      {
        storage->load_fn = load_bin;
        storage->save_fn = save_bin;
      }
      break;

    case STORAGE_TEXT:
      {
        storage->load_fn = load_text;
        storage->save_fn = save_text;
      }
      break;

    default:
      {
        assert(0);
      }
      break;
  }

  ret = storage->load_fn(storage->file);

  h = hash_calc();

  /* Only save if there are more than 1 storages. */

  if ((storage != &g_settings.store[0]) && ((h != g_settings.hash) ||
      (access(file, F_OK) != 0)))
    {
      signotify();
      save();
    }

  g_settings.hash = h;

errout:
  pthread_mutex_unlock(&g_settings.mtx);

  return ret;
}

/****************************************************************************
 * Name: settings_sync
 *
 * Description:
 *    Synchronizes the storage.
 *
 *    wait_dump        - if cached saves are enabled, this determines whether
 *                       the function will wait until the save is actually
 *                       completed or just schedule a new save
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_sync(bool wait_dump)
{
  int ret = OK;
  uint32_t h;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  ret = load();
  if (ret < 0) /* All storages failed to load */
    {
      goto done;
    }

  h = hash_calc();
  if (h != g_settings.hash)
    {
      g_settings.hash = h;
      signotify();
      save();
    }

done:
  pthread_mutex_unlock(&g_settings.mtx);

#ifdef CONFIG_SYSTEM_SETTINGS_CACHED_SAVES
  if ((wait_dump && (ret >= 0)))
    {
      while (g_settings.wrpend)
        {
          usleep(10 * 1000); /* Sleep for 10ms */
        }
    }
#endif

  return ret;
}

/****************************************************************************
 * Name: settings_notify
 *
 * Description:
 *    Registers a task to be notified on any change of the settings.
 *    Whenever any value is changed, a signal will be sent to all
 *    registered threads. Signals are NOT sent when new settings are
 *    created or when the whole storage is cleared.
 *
 * Input Parameters:
 *    none
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_notify(void)
{
  int ret;
  int idx = 0;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  while ((idx < CONFIG_SYSTEM_SETTINGS_MAX_SIGNALS) &&
          g_settings.notify[idx].pid)
    {
      idx++;
    }

  assert(idx < CONFIG_SYSTEM_SETTINGS_MAX_SIGNALS);
  if (idx >= CONFIG_SYSTEM_SETTINGS_MAX_SIGNALS)
    {
      ret = -EINVAL;
      goto errout;
    }

  g_settings.notify[idx].pid = getpid();
  g_settings.notify[idx].signo = CONFIG_SYSTEM_SETTINGS_SIGNO;

errout:
  pthread_mutex_unlock(&g_settings.mtx);

  return OK;
}

/****************************************************************************
 * Name: settings_hash
 *
 * Description:
 *    Gets the hash of the settings storage.
 *
 *    This hash represents the internal state of the settings map. A
 *    unique number is calculated based on the contents of the whole map.
 *
 *    This hash can be used to check the settings for any alterations, i.e.
 *    any setting that may had its value changed since last check.
 *
 * Input Parameters:
 *    none
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_hash(FAR uint32_t *h)
{
  if (!g_settings.initialized)
    {
      assert(0);
    }

  *h = g_settings.hash;

  return OK;
}

/****************************************************************************
 * Name: settings_clear
 *
 * Description:
 *    Clears all settings.
 *    Data in all storages are purged.
 *
 *    Note that if the settings are cleared during the application run-time
 *    (i.e. not during initialization), every access to the settings storage
 *    will fail.
 *
 *    All settings must be created again. This can be done either by calling
 *    Settings_create() again, or by restarting the application.
 *
 * Input Parameters:
 *    none
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_clear(void)
{
  int ret;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  memset(map, 0, sizeof(map));
  g_settings.hash = 0;

  save();

  pthread_mutex_unlock(&g_settings.mtx);

  while (g_settings.wrpend)
    {
      usleep(10 * 1000); /* Sleep for 10ms */
    }

  return OK;
}

/****************************************************************************
 * Name: settings_create
 *
 * Description:
 *    Creates a new setting.
 *
 *    If the setting is found to exist in any of the storages, it will
 *    be loaded. Else, it will be created and the default value will be
 *    assigned.
 *
 * Input Parameters:
 *        key         - the key of the setting.
 *        type        - the type of the setting.
 *        ...         - the default value of the setting.
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_create(FAR char *key, enum settings_type_e type, ...)
{
  int ret = OK;
  FAR setting_t *setting = NULL;
  int j;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  assert(type != SETTING_EMPTY);

  assert(strlen(key));
  if (strlen(key) == 0)
    {
      return -EINVAL;
    }

  assert(strlen(key) < CONFIG_SYSTEM_SETTINGS_KEY_SIZE);
  if (strlen(key) >= CONFIG_SYSTEM_SETTINGS_KEY_SIZE)
    {
      return -EINVAL;
    }

  assert(isalpha(key[0]) && (sanity_check(key) == OK));
  if (!isalpha(key[0]) || (sanity_check(key) < 0))
    {
      return -EINVAL;
    }

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  for (j = 0; j < CONFIG_SYSTEM_SETTINGS_MAP_SIZE; j++)
    {
      if (strcmp(key, map[j].key) == 0)
        {
          setting = &map[j];

          /* We found a setting with this key name */

          goto errout;
        }

      if (map[j].type == SETTING_EMPTY)
        {
          setting = &map[j];
          strncpy(setting->key, key, CONFIG_SYSTEM_SETTINGS_KEY_SIZE);
          setting->key[CONFIG_SYSTEM_SETTINGS_KEY_SIZE - 1] = '\0';

          /* This setting is empty/unused - we can use it */

         break;
        }
    }

  assert(setting);
  if (setting == NULL)
    {
      goto errout;
    }

  if ((setting->type == SETTING_EMPTY) ||
      (setting->type != type))
    {
      bool set_val = false;

      va_list ap;
      va_start(ap, type);

      switch (type)
      {
        case SETTING_STRING:
          {
            FAR char *str = va_arg(ap, FAR char *);

            if ((setting->type == SETTING_STRING) ||
                (setting->type == SETTING_IP_ADDR))
              {
                break;
              }

            set_val = true;
            setting->type = SETTING_STRING;
            ret = set_string(setting, str);
          }
          break;

        case SETTING_INT:
          {
            int i = va_arg(ap, int);

            if ((setting->type == SETTING_INT)  ||
                (setting->type == SETTING_BOOL) ||
                (setting->type == SETTING_FLOAT))
              {
                break;
              }

            set_val = true;
            setting->type = SETTING_INT;
            ret = set_int(setting, i);
          }
          break;

        case SETTING_BOOL:
          {
            int i = va_arg(ap, int);

            if ((setting->type == SETTING_BOOL) ||
                (setting->type == SETTING_INT))
              {
                break;
              }

            set_val = true;
            setting->type = SETTING_BOOL;
            ret = set_bool(setting, i);
          }
          break;

        case SETTING_FLOAT:
          {
            double f = va_arg(ap, double);

            if ((setting->type == SETTING_FLOAT) ||
                (setting->type == SETTING_INT))
              {
                break;
              }

            set_val = true;
            setting->type = SETTING_FLOAT;
            ret = set_float(setting, f);
          }
          break;

        case SETTING_IP_ADDR:
          {
            FAR struct in_addr *ip = va_arg(ap, FAR struct in_addr *);

            if ((setting->type == SETTING_IP_ADDR) ||
                (setting->type == SETTING_STRING))
              {
                break;
              }

            set_val = true;
            setting->type = SETTING_IP_ADDR;
            ret = set_ip(setting, ip);
          }
          break;

        default:
        case SETTING_EMPTY:
          {
            assert(0);
            ret = -EINVAL;
          }
          break;
      }

      va_end(ap);

      if ((ret < 0) || !set_val)
        {
          memset(setting, 0, sizeof(setting_t));
          setting = NULL;
        }
      else
        {
          g_settings.hash = hash_calc();
          save();
        }
    }

errout:
  pthread_mutex_unlock(&g_settings.mtx);

  return ret;
}

/****************************************************************************
 * Name: settings_type
 *
 * Description:
 *    Gets the type of a setting.
 *
 * Input Parameters:
 *    key         - the key of the setting.
 *    type        - pointer to int for the returned setting type
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_type(FAR char *key, FAR enum settings_type_e *stype)
{
  int ret;
  FAR setting_t *setting;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  assert(stype != NULL);
  assert(key != NULL);

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  ret = get_setting(key, &setting);

  if (setting)
    {
      *stype = setting->type;
    }

  pthread_mutex_unlock(&g_settings.mtx);

  return ret;
}

/****************************************************************************
 * Name: settings_get
 *
 * Description:
 *    Gets the value of a setting.
 *
 * Input Parameters:
 *    key         - the key of the setting.
 *    type        - the type of the setting
 *    ...         - pointer to store the setting value plus, if a string
 *                  setting, the length of the string to get
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_get(FAR char *key, enum settings_type_e type, ...)
{
  int ret;
  FAR setting_t *setting;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  assert(type != SETTING_EMPTY);
  assert(key[0] != '\0');

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  ret = get_setting(key, &setting);
  if (ret < 0)
    {
      goto errout;
    }

  va_list ap;
  va_start(ap, type);

  switch (type)
  {
    case SETTING_STRING:
      {
        FAR char *buf = va_arg(ap, FAR char *);
        size_t len = va_arg(ap, size_t);
        ret = (int)get_string(setting, buf, len);
      }
      break;

    case SETTING_INT:
      {
        FAR int *i = va_arg(ap, FAR int *);
        ret = get_int(setting, i);
      }
      break;

    case SETTING_BOOL:
      {
        FAR int *i = va_arg(ap, FAR int *);
        ret = get_bool(setting, i);
      }
      break;

    case SETTING_FLOAT:
      {
        FAR double *f = va_arg(ap, FAR double *);
        ret = get_float(setting, f);
      }
      break;

    case SETTING_IP_ADDR:
      {
        FAR struct in_addr *ip = va_arg(ap, FAR struct in_addr *);
        ret = get_ip(setting, ip);
      }
      break;

    default:
      {
        assert(0);
      }
      break;
  }

  va_end(ap);

errout:
  pthread_mutex_unlock(&g_settings.mtx);

  return ret;
}

/****************************************************************************
 * Name: settings_set
 *
 * Description:
 *    Sets the value of a setting. The type can be changed, within limits,
 *    here too.
 *
 * Input Parameters:
 *    key         - the key of the setting.
 *    type        - the type of the setting
 *    ...         - the new value of the setting.
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_set(FAR char *key, enum settings_type_e type, ...)
{
  int ret;
  FAR setting_t *setting;
  uint32_t h;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  assert(type != SETTING_EMPTY);
  assert(key[0] != '\0');

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  ret = get_setting(key, &setting);
  if (ret < 0)
    {
      goto errout;
    }

  va_list ap;
  va_start(ap, type);

  switch (type)
  {
    case SETTING_STRING:
      {
        FAR char *str = va_arg(ap, FAR char *);
        ret = set_string(setting, str);
      }
      break;

    case SETTING_INT:
      {
        int i = va_arg(ap, int);
        ret = set_int(setting, i);
      }
      break;

    case SETTING_BOOL:
      {
        int i = va_arg(ap, int);
        ret = set_bool(setting, i);
      }
      break;

    case SETTING_FLOAT:
      {
        double f = va_arg(ap, double);
        ret = set_float(setting, f);
      }
      break;

    case SETTING_IP_ADDR:
      {
        FAR struct in_addr *ip = va_arg(ap, FAR struct in_addr *);
        ret = set_ip(setting, ip);
      }
      break;

    default:
      {
        assert(0);
      }
      break;
  }

  va_end(ap);

  if (ret >= 0)
    {
      h = hash_calc();
      if (h != g_settings.hash)
        {
          g_settings.hash = h;

          signotify();
          save();
        }
    }

errout:
  pthread_mutex_unlock(&g_settings.mtx);

  return ret;
}

/****************************************************************************
 * Name: settings_iterate
 *
 * Description:
 *    Gets a copy of a setting at the specified position. It can be used to
 *    iterate over the settings map, by using successive values of idx.
 *
 * Input Parameters:
 *    idx         - the iteration index for the setting.
 *    setting     - pointer to return the setting value
 *
 * Returned Value:
 *    Success or negated failure code
 *
 ****************************************************************************/

int settings_iterate(int idx, FAR setting_t *setting)
{
  int ret;

  if (!g_settings.initialized)
    {
      assert(0);
    }

  assert(setting);

  if ((idx < 0) || (idx >= CONFIG_SYSTEM_SETTINGS_MAP_SIZE))
    {
      memset(setting, 0, sizeof(setting_t));
      return -EINVAL;
    }

  ret = pthread_mutex_lock(&g_settings.mtx);
  if (ret < 0)
    {
      return ret;
    }

  memcpy(setting, &map[idx], sizeof(setting_t));

  if (map[idx].type == SETTING_EMPTY)
    {
      ret = -ENOENT;
    }

  pthread_mutex_unlock(&g_settings.mtx);

  return ret;
}