# internal shortcut alias
settings() {
  rlx.commands.settings "$@";
}

rlx.commands.settings() {
  if [ $# -eq 0 ]; then
    settings.commands.get;
  else
    local settings_namespace="settings.commands";
    local cmd="${1:-}";
    shift;
    if ! method.exists? "${settings_namespace}.${cmd}"; then
      console error -- "unknown settings command %s" "${cmd}";
    else
      delegate "${settings_namespace}" "${cmd}" "$@";
    fi
  fi
}

# print default settings
settings.commands.defaults() {
  local value key;
  local keys=( ${!rlx_defaults[@]} );
  local IFS=$'\n';
  keys=( $( echo "${keys[*]}" | sort ) );
  unset IFS;
  for key in ${keys[@]:-};
    do
      value="${rlx_defaults[$key]:-}";
      console print -- "${key}=%s" "${value}";
  done
}

# reset to default settings
settings.commands.reset() {
  local value key;
  local keys;
  if [ $# -eq 0 ]; then
    keys=( ${!rlx_defaults[@]} );
  else
    keys=( "$@" );
  fi
  for key in ${keys[@]:-};
    do
      value="${rlx_defaults[$key]:-}";
      if [ -z "${value}" ]; then
        console error -- "unknown setting %s" "${key}";
        continue;
      fi
      settings.commands.set "${key}" "${value}";
  done
}

# get setting(s)
settings.commands.get() {
  local value key="${1:-}";
  if [ $# -eq 0 ]; then
    local keys=( ${!rlx_settings[@]} );
    local IFS=$'\n';
    keys=( $( echo "${keys[*]}" | sort ) );
    unset IFS;
    for key in ${keys[@]:-};
      do
        value="${rlx_settings[$key]}";
        console print -- "${key}=%s" "${value}";
    done
  else
    while [ -n "${1:-}" ];
      do
        key="${1:-}";
        value="${rlx_settings[$key]:-}";
        if [ -z "${value}" ]; then
          console error -- "unknown settings key %s" "${key}";
        else
          console print -- "${key}=%s" "${value}";
        fi
        shift;
    done
  fi
}

# set a setting by key
settings.commands.set() {
  local key="${1:-}"; shift;
  local value="$*";
  if [ -z "${key}" ]; then
    console error -- "no key specified";
  elif [ -z "${value}" ]; then
    console error -- "no value specified";
  elif [ -z "${rlx_settings[$key]:-}" ]; then
    console error -- "unknown settings key %s" "${key}";
  else
    if method.exists? "settings.hooks.${key}"; then
      if settings.hooks.${key} "${key}" "${value}"; then
        settings.set "${key}" "${value}";
      fi
    else
      settings.set "${key}" "${value}";
    fi
    if method.exists? "settings.hooks.post.${key}"; then
      settings.hooks.post.${key} "${key}" "${value}";
    fi
  fi
}

# print contents of the rc file
settings.commands.print() {
  if [ ! -f "${files[rc]}" ]; then
    console error -- "missing %s" "${files[rc]}";
  else
    cat "${files[rc]}";
  fi
}

# save the rc file
settings.commands.save() {
  local key value;
  if [ ! -f "${files[rc]}" ]; then
    touch "${files[rc]}" \
      || { 
        console error -- "could not create %s" \
        "${files[rc]}" \
        && return 0;
      };
  fi
  printf "" >| "${files[rc]}" \
    || {
      console error -- "could not clear %s" "${files[rc]}" \
      && return 0;
    }
  for key in ${!rlx_settings[@]};
    do
      value="${rlx_settings[$key]}";
      key="${settings_prefix}${key}";
      echo "export ${key}='${value}';" >> "${files[rc]}" \
        || {
          console error -- "error writing to %s" "${files[rc]}" \
          && return 0;
        }
  done
  console info -- "saved settings to %s" "${files[rc]}";
}

settings.commands.load() {
  if [ -f "${files[rc]}" ]; then
    . "${files[rc]}" \
      || console error -- "failed to load %s" "${files[rc]}";
  fi
  settings.assign;
}

## PRIVATE

# overwrite settings from environment variables
settings.assign() {
  local k v e;
  for k in "${!rlx_defaults[@]}";
    do
      v="${rlx_defaults[$k]:-}";
      variable.get "${settings_prefix}${k}" "e";
      #echo "assign with key $k=$v ($e)";
      if [ -n "${e}" ]; then
        rlx_settings[$k]="${e}";
      else
        rlx_settings[$k]="${v}";
      fi
  done
}

# set a setting
settings.set() {
  local print="${print:-true}";
  rlx_settings[$key]="${value}";
  if $print; then
    settings.commands.get "${key}";
  fi
}

# configure default settings
settings.defaults() {
  rlx_defaults[debug]="false";
  rlx_defaults[verbose]="false";
  rlx_defaults[network]="false";
  rlx_defaults[reqheaders]="false";
  rlx_defaults[resheaders]="false";

  rlx_defaults[tabstops]=2;
  rlx_defaults[lang]="perl";
  rlx_defaults[templatelang]="node";

  rlx_defaults[shstyle]="${defaults[shstyle]}";
  rlx_defaults[shlang]="${defaults[shlang]}";
  rlx_defaults[hilite]="${defaults[hilite]}";
  rlx_defaults[hiliteopts]="${defaults[hiliteopts]}";

  rlx_defaults[color]="true";
  rlx_defaults[server]="http://localhost:5984";
  rlx_defaults[database]="false";
  rlx_defaults[userns]="org.couchdb.user";
  rlx_defaults[editor]="vim";
  #rlx_defaults[editopts]="-c \"set nobackup\" -c \"set noswapfile\" +file %s";
  rlx_defaults[editopts]="+file %s";
  rlx_defaults[pager]="less";
  rlx_defaults[usepager]="false";
  rlx_defaults[lint]="true";

  rlx_defaults[templates]="false";

  rlx_defaults[savepretty]="true";
  rlx_defaults[mime]="application/octet-stream";
  rlx_defaults[dotglob]="false";
  rlx_defaults[progress]="false";

  # template identifiers
  rlx_defaults[tpldoc]="doc";
  rlx_defaults[tplrepl]="repl";
  rlx_defaults[tplreplfilter]="repl-filter";
  rlx_defaults[tplrepldocs]="repl-docs";

  # http
  rlx_defaults[timeout]=1;

  # browser
  rlx_defaults[browser]="w3m %s";

  # view parameters
  rlx_defaults[limit]="10";
  rlx_defaults[descending]="false";

  settings load;

  # hooks must be called at startup
  local key;
  for key in "${!rlx_settings[@]}";
    do
      settings.hook "${key}" "${rlx_settings[$key]:-}";
  done
}

## SETTINGS COMMAND SHORTCUTS

rlx.commands.color() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set color $value;
  if ! $value && [ "${rlx_settings[hilite]:-}" != false ]; then
    export hilitecache="${rlx_settings[hilite]:-}";
  elif $value; then
    value="${hilitecache:-}";
  fi
  settings.commands.set hilite $value;
}

rlx.commands.pygments() {
  settings.commands.set hilite "${defaults[pygments]}";
  settings.commands.set hiliteopts "${defaults[pygmentsopts]}";
}

rlx.commands.hilite() {
  settings.commands.set hilite "${defaults[hilite]}";
  settings.commands.set hiliteopts "${defaults[hiliteopts]}";
}

rlx.commands.lang() {
  local lang="${1:-${rlx_defaults[lang]:-}}";
  local templatelang="${2:-${lang:-}}";
  if [ $# -eq 0 ]; then
    settings get "lang" "templatelang";
  else
    settings set lang "${lang}";
    settings set templatelang "${templatelang}";
  fi
}

rlx.commands.limit() {
  local value="${1:-}";
  if [ -z "${value}" ]; then
    settings get limit;
  else
    validate.integer "limit" "${value}" || return 1;
    settings set limit "${value}";
  fi
}

rlx.commands.headers() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set reqheaders $value;
  settings.commands.set resheaders $value;
}

rlx.commands.pager() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set usepager $value;
}

rlx.commands.progress() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set progress $value;
}

rlx.commands.verbose() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set verbose $value;
}

rlx.commands.network() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set network $value;
}

rlx.commands.debug() {
  local value="${1:-on}";
  [[ "${value}" == on  ]] && value=true || value=false;
  settings.commands.set debug $value;
}

## HOOKS

# run a hook
settings.hook() {
  local key="${1:-}";
  local value="${2:-}";
  if method.exists? "settings.hooks.${key}"; then
    settings.hooks.${key} "${key}" "${value}";
    if method.exists? "settings.hooks.post.${key}"; then
      settings.hooks.post.${key} "${key}" "${value}";
    fi
  fi
}

validate.integer() {
  local key="${1:-}";
  local value="${2:-}";
  if [[ ! "${value}" =~ ^[0-9]+$ ]]; then
    console error -- "integer expected for %s, got %s" \
      "${key}" "${value}";
    return 1;
  fi
}

validate.boolean() {
  local key="${1:-}";
  local value="${2:-}";
  if [ "${value}" != true ] && [ "${value}" != false ]; then
    console error -- "boolean expected for %s, got %s" \
      "${key}" "${value}";
    return 1;
  fi
}

validate.directory() {
  local key="${1:-}";
  local value="${2:-}";
  if [ ! -d "${value}" ] || [ ! -w "${value}" ]; then
    console error -- "writable directory expected for %s, got %s" \
      "${key}" "${value}";
    return 1;
  fi
}

validate.executable() {
  local key="${1:-}";
  local value="${2:-}";
  executable.validate --test "${value}";
  if [ -z "${executables[${value}]:-}" ]; then
    console error -- "executable expected for %s, but %s is not available" \
      "${key}" "${value}";
    return 1;
  fi
}

settings.hooks.timeout() {
  local key="${1:-}";
  local value="${2:-}";
  validate.integer "${key}" "${value}" || return 1;
}

settings.hooks.templates() {
  local key="${1:-}";
  local value="${2:-}";
  if [ "${value}" == false ]; then
    return 0;
  fi
  validate.directory "${key}" "${value}" || return 1;
}

settings.hooks.limit() {
  local key="${1:-}";
  local value="${2:-}";
  validate.integer "${key}" "${value}" || return 1;
  if [ ${value} -eq 0 ]; then
    console error -- "page limit may not be zero";
    return 1;
  fi
}

settings.hooks.reqheaders() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.resheaders() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.progress() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.descending() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.editor() {
  local key="${1:-}";
  local value="${2:-}";
  validate.executable "${key}" "${value}" || return 1;
}

settings.hooks.lang() {
  local key="${1:-}";
  local value="${2:-}";
  local languages=( perl python ruby node false );
  if ! array.contains? "${value}" "${languages[@]}"; then
    console error -- "invalid value %s for %s, must be one of %s" \
      "${value}" "${key}" "${languages[*]}";
    return 1;
  fi
  validate.executable "${key}" "${value}" || return 1;
}

settings.hooks.templatelang() {
  local key="${1:-}";
  local value="${2:-}";
  local tpl_languages=( perl python ruby node false );
  if ! array.contains? "${value}" "${tpl_languages[@]}"; then
    console error -- "invalid value %s for %s, must be one of %s" \
      "${value}" "${key}" "${tpl_languages[*]}";
    return 1;
  fi
  validate.executable "${key}" "${value}" || return 1;
}

settings.hooks.hilite() {
  local key="${1:-}";
  local value="${2:-}";
  if ! array.contains? "${value}" "${highlighters[@]}"; then
    console error -- "invalid value %s for %s, must be one of %s" \
      "${value}" "${key}" "${highlighters[*]}";
    return 1;
  fi
  validate.executable "${key}" "${value}" || return 1;
}

settings.hooks.post.progress() {
  http[printstderr]="${rlx_settings[progress]:-true}";
}

settings.hooks.post.verbose() {
  flags[verbose]="${rlx_settings[verbose]:-false}";
}

settings.hooks.post.debug() {
  flags[debug]="${rlx_settings[debug]:-false}";
}

settings.hooks.post.timeout() {
  couchdb[timeout]="${rlx_settings[timeout]:-5}";
}

settings.hooks.pager() {
  local key="${1:-}";
  local value="${2:-}";
  validate.executable "${key}" "${value}" || return 1;
}

settings.hooks.usepager() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.lint() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.savepretty() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.dotglob() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.verbose() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.network() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
  couchdb[verbose]="${value}";
}

settings.hooks.debug() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
}

settings.hooks.color() {
  local key="${1:-}";
  local value="${2:-}";
  validate.boolean "${key}" "${value}" || return 1;
  if ${value}; then
    export sprintf_color=always;
  else
    export sprintf_color=never;
  fi
  rlx.prompt;
}

settings.hooks.tabstops() {
  local key="${1:-}";
  local value="${2:-}";
  validate.integer "${key}" "${value}" || return 1;
}
