require.library alias;
require.library attachment;
require.library auth;
require.library bulk;
require.library config;
require.library db;
require.library debug;
require.library design;
require.library doc;
require.library editor;
require.library lint;
require.library paginate;
require.library prompt;
require.library replicate;
require.library server;
require.library settings;
require.library template;

rlx() {
  executable.validate openssl curl tee man cat cmp;

  process.signal.on "rlx.int" int;
  process.signal.on "rlx.exit" exit;
  process.on int exit;

  local interpreter;
  local commands_namespace="rlx.commands";
  local rlx_command rlx_command_options;

  declare -Ag query_languages;
  query_languages[javascript]="js";

  declare -Ag extensions;
  extensions[perl]="pl";
  extensions[node]="js";
  extensions[python]="py";
  extensions[ruby]="rb";

  declare -Ag regexp;
  regexp[nmpattern]="([a-zA-Z0-9-]+)";
  regexp[name]="^${regexp[nmpattern]}$";
  regexp[alias]="^(@)${regexp[nmpattern]}$";
  regexp[scheme]="^(https?://)";
  regexp[cdscheme]="^(https?://)(.*)";
  regexp[sprintf]="%s";
  regexp[jsondoc]="\.json$";
  regexp[comment]="^#";
  regexp[limit]="(limit)=([0-9]+)";
  regexp[hostname]="${regexp[scheme]}([^/:]+)(/|:)?";
  regexp[domain]="${regexp[scheme]}([^/:]+)(:[0-9]+)?";
  
  local quits=( q quit exit );
  local highlighters=( source-highlight pygmentize false );

  declare -Ag flags;
  flags[success]=false;
  flags[verbose]=false;
  flags[debug]=false;

  auth.clear;

  declare -Ag files;
  declare -Ag directories;
  rlx.home;

  # user data
  directories[downloads]="${files[user]}/downloads";
  directories[attachments]="${files[user]}/attachments";
  directories[aliases]="${files[user]}/aliases";
  directories[query]="${files[user]}/query";
  directories[design]="${files[user]}/design";

  files[history]="${files[user]}/history.log";
  files[rc]="${files[user]}/rlxrc";
  files[vimrc]="${files[user]}/.vimrc";

  # system data
  directories[template]="${process_dirs[lib]}/template";
  directories[tmp]="${files[home]}/tmp/session/$$";

  directories[backup]="${directories[tmp]}/backup";
  directories[http]="${directories[tmp]}/http";

  files[alldocs]="${directories[tmp]}/alldocs.json";
  files[cookie]="${directories[tmp]}/cookie.txt";
  files[response]="${directories[tmp]}/response.json";
  files[document]="${directories[tmp]}/document.json";
  files[input]="${directories[tmp]}/input.json";
  files[pretty]="${directories[tmp]}/pretty.json";
  files[lint]="${directories[tmp]}/lint.log";

  declare -Ag defaults;
  defaults[object]="{}";
  defaults[extension]="json";
  defaults[username]="anonymous";
  defaults[version]=$( cat "${process_dirs[version]}" );
  defaults[shstyle]="${process_dirs[root]}/highlight/json.style";
  defaults[shlang]="${process_dirs[root]}/highlight/json.lang";
  defaults[hilite]="source-highlight";
  defaults[hiliteopts]="-f esc -s json";
  defaults[hiliteopts]+=" --style-file \"${defaults[shstyle]}\"";
  defaults[hiliteopts]+=" --lang-def \"${defaults[shlang]}\"";
  defaults[pygments]="pygmentize";
  defaults[pygmentsopts]="-l json";
  defaults[savename]="couchdb.json";
  defaults[usertemplates]="rlx-template";
  defaults[login.attempts]=0;
  defaults[login.attempts.maximum]=3;

  defaults[template]="${directories[template]}";

  couchdb[ua]="${process_name}/${defaults[version]}";
  couchdb[cookie-jar]="${files[cookie]}";

  paginate.reset;

  local settings_prefix="rlx_";
  declare -A rlx_defaults;
  declare -A rlx_settings;
  settings.defaults;

  declare -Ag info;
  info[server]="${rlx_settings[server]:-}";
  info[database]="${rlx_settings[database]:-}";
  info[username]="";
  info[version]="";

  if [ ! -d "${files[home]}" ]; then
    mkdir -p "${files[home]}" \
      || console quit 1 -- "could not create %s" "${files[home]}";
  fi

  rlx.startup;

  prompt line \
    --response=rlx.cli.response \
    --hist-file="${files[history]}" \
    --quit="${quits[*]}" --infinite --id=rlx;
}

rlx.home() {
  # handle rlx_home environment variable
  local file="${rlx_home:-${process_dirs[data]}}";
  tilde.expand;
  if [ -z "${rlx_home:-}" ]; then
    fs.mkdirs "${file}";
  fi
  if [ ! -d "${file}" ] || [ ! -w "${file}" ]; then
    console quit 1 -- "home directory %s is not a writable directory" "${file}";
  fi
  file="${file%/}";
  fs.path.expand "${file}" "file";
  files[home]="${file}";

  # handle rlx_user environment variable
  file="${rlx_user:-~/.rlx}";
  tilde.expand;
  if [ -z "${rlx_user:-}" ]; then
    fs.mkdirs "${file}";
  fi
  if [ ! -d "${file}" ] || [ ! -w "${file}" ]; then
    console quit 1 -- "user directory %s is not a writable directory" "${file}";
  fi
  file="${file%/}";
  fs.path.expand "${file}" "file";
  files[user]="${file}";
}

# commands executed at startup
rlx.startup() {
  # create directories, must be done before executing any command
  local d dirkeys=( downloads attachments aliases backup tmp http );
  for d in "${dirkeys[@]}"
    do
      d="${directories[$d]:-}";
      if [ -n "${d}" ] && [ ! -d "${d}" ]; then
        fs.mkdirs "${d}";
      fi
  done

  # initialize where http module files are stored
  http init "${directories[http]}";
  http[body.file]="${files[response]}";

  # attempt to use a default database if set
  if [ -n "${info[server]:-}" ] \
    && [ "${info[server]}" != false ]; then
    local url="${info[server]}";
    if [ -n "${info[database]:-}" ] \
      && [ "${info[database]}" != false ]; then
        url+="/${info[database]}";
    fi
    rlx.commands.cd "${url}";
  fi
  #if [ ! -f "${files[vimrc]}" ]; then
    cp "${process_dirs[lib]}/vim/vimrc" "${files[vimrc]}";
  #fi
}

# show man page
rlx.commands.help() {
  if [ $# -eq 0 ]; then
    help.man.show.default false;
  else
    help.man.show "${1:-}" false;
  fi
}

## PRIVATE

# handle cli input
rlx.cli.response() {
  local input="${1:-}";

  if [ ! -d "${files[home]}" ] \
    || [ ! -d "${directories[tmp]}" ] \
    || [ ! -w "${directories[tmp]}" ]; then
      console quit 1 -- "aborting, program directory is missing or not writable";
  fi

  if [[ "${input}" =~ ${regexp[comment]} ]]; then
    return 0;
  fi
  if array.contains? "${input}" "${quits[@]}" \
    || [ -z "${input}" ]; then
    return 0;
  fi
  #echo "command got input $input"
  local options=( $input );
  set -- "${options[@]:-}";
  local cmd="${1:-}";
  shift;
  unset -v options input;
  if ! method.exists? "${commands_namespace}.${cmd}"; then
    console error -- "unknown command %s" "${cmd}";
  else
    delegate "${commands_namespace}" "$cmd" "$@";
    # NOTE: commands generally show more specific errors
    # NOTE: this allows a command to defer to a generic error 
    if [ $? -eq 2 ]; then
      console error -- "command %s %s failed" "$cmd" "$*";
    fi
  fi
}

# handle a server response
rlx.response() {
  flags[success]=false;
  if [ "${http[status]:-000}" == "000" ]; then
    return 0;
  fi
  if [ "${rlx_settings[reqheaders]:-}" == true ]; then
    rlx.reqheaders;
  fi
  if [ "${rlx_settings[resheaders]:-}" == true ]; then
    rlx.resheaders;
  fi
  declare -A request;
  request[host]="${http_req_headers[Host]:-}";
  request[host]="${request[host]# }";
  if [[ "${couchdb[url]}" =~ ${regexp[scheme]} ]]; then
    request[server]="${BASH_REMATCH[1]}${request[host]}";
  else
    console error -- "could not determine protocol of last request";
  fi
  if [ ${http[status]} -eq 401 ]; then
    console info -- "authorization required %s" "${request[host]}";
    auth session login "${request[server]}" "" true;
    return 1;
  fi
  #echo "got response status: ${http[status]}";
  if [ "${http[status]}" == 200  ] \
    || [ "${http[status]}" == 201 ] \
    || [ "${http[status]}" == 202 ]; then
    flags[success]=true;
  fi
  if [[ "${http[status]}" =~ ^4 ]]; then
    rlx.debug "error response status %s" "${http[status]}";
    #echo "couchdb verb ${couchdb[verb]:-}"
    rlx.json.error;
  fi
}

rlx.json.error() {
  if [ "${http[status]}" == 404 ] \
    && [ "${couchdb[verb]:-}" == head ]; then
    console error -- "%s missing" "not_found";
    return 1;
  fi
  rlx.interpreter error;
  local parts=( $( "$interpreter" < "${files[response]}" || false ) );
  if [ $? -gt 0 ]; then
    console error -- "error processing response error document";
    rlx.debug "%s encountered an error with the document %s" \
      "${interpreter}" "${files[response]}";
    return 1;
  fi
  if [ ${#parts[@]} -gt 0 ]; then
    local error="${parts[0]:-}"; unset parts[0];
    local reason="${parts[@]:-}";
    console error -- "%s ${reason}" "${error}";
  fi
}

# runs a command using the couchdb(3) module
# this is used so that when authentication is
# required we can re-run the last command after
# capturing the username/password pair
rlx.run() {
  local cmd="${1:-}"; shift;
  if [ "${cmd}" == false ]; then
    cmd="${1:-}"; shift;
  else
    rlx_command="${cmd}";
    rlx_command_options=( "$@" );
  fi
  "$cmd" "$@";
  rlx.response;
}

# repeat the last command
rlx.repeat() {
  if [ -n "${rlx_command:-}" ]; then
    rlx.run "$rlx_command" ${rlx_command_options[@]:-};
  fi
}

rlx.json.enabled() {
  local binary="${1:-}";
  if [ -z "${binary}" ] || [ "${binary}" == false ]; then
    return 1;
  fi
  command -v "${binary}" \
    >/dev/null 2>&1 || {
      console warn -- "%s is not available" "${binary}" && false
    }
}

rlx.json.compact() {
  local input="${1:-${files[document]}}";
  local output="${2:-${files[input]}}";
  export JSON_INDENT=0;
  rlx.interpreter lint;
  "$interpreter" < "${input}" >| "${output}" \
    || {
      console error -- "failed to compact %s to %s" "${input}" "${output}";
      return 1;
    };
  export JSON_INDENT="${rlx_settings[tabstops]:-}";
  return 0;
}

# SEE: http://stackoverflow.com/questions/352098/how-to-pretty-print-json-from-the-command-line
rlx.json.print() {
  local file="${1:-${files[response]:-}}";
  local binary="${2:-lint}";
  export JSON_INDENT="${rlx_settings[tabstops]:-}";
  local color=false;
  local pretty=false;
  local colorcmd="";
  if rlx.json.enabled "${rlx_settings[hilite]:-}"; then
    color=true;
    colorcmd=( "${rlx_settings[hilite]}" "${rlx_settings[hiliteopts]:-}" );
  fi
  #echo "using binary $binary"
  rlx.debug "binary %s" "${binary}";
  unset interpreter;
  rlx.interpreter "${binary}";
  rlx.debug "interpreter %s" "${interpreter}";
  #echo "interpreter: $interpreter"
  #if [ "${interpreter}" != cat  ] && [ ! -x "${interpreter}" ]; then
    #console error -- "missing interpreter %s" "${interpreter}";
    #return 1;
  #fi
  if rlx.json.enabled "${rlx_settings[lang]:-}" \
    && [ -x "${interpreter}" ]; then
    pretty=true;
  fi
  local cmd="cat \"${file}\"";
  if $pretty; then
    cmd="$interpreter < \"${file}\"";
  fi
  if ${color}; then
    cmd+=" | ${colorcmd[@]}";
  fi
  if [ "${rlx_settings[usepager]:-}" == true ] \
    && rlx.json.enabled "${rlx_settings[pager]:-}"; then
    cmd+=" | ${rlx_settings[pager]}";
  fi
  /usr/bin/env bash -c "${cmd}";
}

rlx.interpreter() {
  local name="${1:-lint}";
  #echo "using name $name"
  local assign="${2:-interpreter}";
  local lang="${3:-${rlx_settings[lang]:-}}";
  local path="${process_dirs[bin]}/${lang}/${name}";
  local binary=cat;
  #echo "path is $path"
  #export interpreter=cat;
  if [ -n "${lang}" ] && [ "${lang}" != false ]; then
    if [ -x "${path}" ]; then
      binary="${path}";
    else
      console warn -- "interpreter %s not found" "${path}";
    fi
  fi
  variable.set "${assign}" "${binary}";
  #echo "interpreter is ${interpreter}"
}


# standard confirmation prompt
# reject command
rlx.rejected() {
  rlx.prompt;
}

rlx.unquote() {
  local quoted="${1:-}";
  local output="${2:-}";
  quoted=${quoted%'"'};
  quoted=${quoted#'"'};
  variable.set "${output}" "${quoted}";
}

rlx.quote() {
  local value="${1:-}";
  local output="${2:-}";
  rlx.interpreter stringify;
  value=$( "$interpreter" true <<< "${value}" || false );
  #echo "quoted value $value";
  if [ $? -gt 0 ]; then
    console error -- "failed to quote value %s" "${value}";
    return 1;
  fi
  variable.set "${output}" "${value}";

  # NOTE: initial implementation, probably faster, may restore
  #variable.set "${output}" "\"${value}\"";
}

# print request headers
rlx.reqheaders() {
  console print -- "%s" "${http_req_status:-}";
  local k v;
  for k in ${!http_req_headers[@]}
    do
      v="${http_req_headers[$k]}";
      console print "${k}:%s" "${v}";
  done
}

# print response headers
rlx.resheaders() {
  console print -- "%s" "${http_res_status:-}";
  local k v;
  for k in ${!http_res_headers[@]}
    do
      v="${http_res_headers[$k]}";
      console print "${k}:%s" "${v}";
  done
}

rlx.file.cleanup() {
  local file="${1:-}";
  if [ -f "${file}" ]; then
    rm "${file}" 2> /dev/null;
  fi
}

rlx.browser.launch() {
  local url="${1:-}";
  if [ -n "${url}" ]; then
    local browser="${rlx_settings[browser]:-}";
    if [ -n "${browser}" ] \
      && [ "${browser}" != false ] \
      && [[ "${browser}" =~ ${regexp[sprintf]} ]]; then
      browser=( $( printf "${browser}" "${url}" ) );
      local cmd="${browser[0]}";
      local opts="${browser[@]:1}";
      executable.validate --test "${cmd}";
      if [ -z "${executables[$cmd]:-}" ]; then
        console error -- "browser %s is not available" "${cmd}";
      else
        "${executables[$cmd]}" $opts; 
      fi
    fi
  fi
}

rlx.debug() {
  if [ $# -gt 0 ] && ${flags[debug]}; then
    console info --prefix="[debug]" --text=cyan -- "$@" >&2;
  fi
}

## SIGNALS

rlx.int() {
  printf "\n";
  console quit 1;
}

rlx.exit() {
  local signal="$1";
  local tmp="${directories[tmp]:-}";
  if [ -d "${tmp}" ]; then
    # TODO: only show this when debug setting is true
    rlx.debug "clean %s" "${tmp}";
    rm -rf "${tmp}";
  fi
}
