require.once net/http;
require.once net/url;

declare -Ag couchdb;
couchdb[progress]=true;
couchdb[ua]="";
couchdb[timeout]="0";
couchdb[cookie-jar]="";
couchdb[cookie]="";
couchdb[url]="";
couchdb[verb]="";
couchdb[verbose]=false;
couchdb[verbose.background]=black;
couchdb[accept]="${mime_types[json]}";

# url segments
couchdb[utils]="_utils";
couchdb[security]="_security";
couchdb[docs]="docs";
couchdb[session]="_session";
couchdb[tasks.active]="_active_tasks";
couchdb[stats]="_stats";
couchdb[log]="_log";
couchdb[config]="_config";
couchdb[restart]="_restart";
couchdb[uuids]="_uuids";
couchdb[all.dbs]="_all_dbs";
couchdb[purge]="_purge";
couchdb[changes]="_changes";
couchdb[ensure.full.commit]="_ensure_full_commit";
couchdb[revs.limit]="_revs_limit";
couchdb[compact]="_compact";
couchdb[view.cleanup]="_view_cleanup";
couchdb[all.docs]="_all_docs";
couchdb[bulk.docs]="_bulk_docs";
couchdb[design]="_design";
couchdb[info]="_info";
couchdb[view]="_view";
couchdb[show]="_show";
couchdb[list]="_list";

# centralized entry point for couchdb(3)
# http(3) requests so that it's easier to
# add before/after debugging statements
couchdb.run() {
  local verb="${1:-}";
  local url="${2:-}";
  if ${couchdb[verbose]}; then
    # head requests are actually GET with --head
    # change verb to reflect that only the headers are fetched
    if array.contains? "--head" "$@"; then
      verb="HEAD";
    fi
    # NOTE: must escape URL encoded values to prevent
    # NOTE: printf from interpreting them
    #url="${url//%/%%/}";
    console info --prefix="[${verb}]" \
      --background0="${couchdb[verbose.background]}" \
      -- "%s" "${url}" >&2;
  fi

  local opts=( "$@" );
  if [ -n "${couchdb[ua]}" ]; then
    opts+=( --user-agent "${couchdb[ua]}" );
  fi
  if [ ${couchdb[timeout]:-0} -gt 0 ]; then
    opts+=( --connect-timeout "${couchdb[timeout]}"  );
  fi
  # add session cookie authentication information
  if [ -n "${couchdb[cookie]:-}" ]; then
    opts+=(--cookie "${couchdb[cookie]}");
  fi
  #if [ -n "${couchdb[accept]}" ]; then
    #opts+=(--header "Accept: ${couchdb[accept]}");
  #fi
  couchdb[url]="${url}";
  couchdb[verb]="${verb,,}";
  http.curl "${opts[@]}";
}

couchdb.session.login() {
  local host="${1:-}";
  local user="${2:-}";
  local pass="${3:-}";
  if [ -n "$host" ] && [ -n "$user" ] && [ -n "$pass" ]; then
    local opts=(
      --header "Content-Type: application/x-www-form-urlencoded"
      -d "name=${user}&password=${pass}"
    );
    if [ -n "${couchdb[cookie-jar]}" ]; then
      opts+=(--cookie-jar "${couchdb[cookie-jar]}");
    fi
    couchdb.run "POST" "${host}/${couchdb[session]}" "${opts[@]}";
  fi
}

couchdb.docs.head() {
  local host="${1:-}";
  local url="${host}/${couchdb[utils]:-}/${couchdb[docs]:-}/";
  local redirects="${http_redirects:-true}";
  http_redirects=false;
  couchdb.run "GET" "${url}" --head;
  http_redirects="${redirects}";
}

couchdb.session() {
  local host="${1:-}";
  couchdb.run "GET" "${host}/${couchdb[session]}";
}

couchdb.session.logout() {
  local host="${1:-}";
  couchdb.run "DELETE" "${host}/${couchdb[session]}";
}

couchdb.tasks() {
  local host="${1:-}";
  couchdb.run "GET" "${host}/${couchdb[tasks.active]}";
}

couchdb.stats() {
  local host="${1:-}";
  couchdb.run "GET" "${host}/${couchdb[stats]}";
}

couchdb.log() {
  local host="${1:-}";
  local bytes="${2:-}";
  local url="${host}/${couchdb[log]}";
  if [[ "${bytes}" =~ ^[0-9]+$ ]]; then
    url encode "${bytes}" "bytes";
    url+="?bytes=${bytes}";
  fi
  couchdb.run "GET" "${url}";
}

couchdb.config.get() {
  local host="${1:-}";
  local section="${2:-}";
  local key="${3:-}";
  local url="${host}/${couchdb[config]}";
  if [ -n "${section}" ]; then
    url+="/${section}";
  fi
  if [ -n "${key}" ]; then
    url+="/${key}";
  fi
  couchdb.run "GET" "${url}";
}

couchdb.config.rm() {
  local host="${1:-}";
  local section="${2:-}";
  local key="${3:-}";
  local url="${host}/${couchdb[config]}";
  if [ -n "${section}" ]; then
    url+="/${section}";
  fi
  if [ -n "${key}" ]; then
    url+="/${key}";
  fi
  couchdb.run "DELETE" "${url}";
}

couchdb.config.set() {
  local host="${1:-}";
  local section="${2:-}";
  local key="${3:-}";
  local file="${4:-}";
  local url="${host}/${couchdb[config]}";
  if [ -n "${section}" ]; then
    url+="/${section}";
  fi
  if [ -n "${key}" ]; then
    url+="/${key}";
  fi
  couchdb.run "PUT" "${url}" --data-binary "@${file}" \
    --header "Content-Type: ${mime_types[json]}";
}

couchdb.restart() {
  local host="${1:-}";
  couchdb.run "POST" "${host}/${couchdb[restart]}";
}

couchdb.uuids() {
  local host="${1:-}";
  local count="${2:-}";
  local url="${host}/${couchdb[uuids]}";
  if [[ "${count}" =~ ^[0-9]+$ ]]; then
    url encode "${count}" "count";
    url+="?count=${count}";
  fi
  couchdb.run "GET" "${url}";
}

couchdb.security.get() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "GET" "${host}/${db}/${couchdb[security]}";
}

couchdb.security.set() {
  local host="${1:-}";
  local db="${2:-}";
  local file="${3:-}";
  url encode "${db}" "db";
  couchdb.run "PUT" "${host}/${db}/${couchdb[security]}" \
    --data-binary "@${file}";
}

couchdb.db.list() {
  local host="${1:-}";
  couchdb.run "GET" "${host}/${couchdb[all.dbs]}";
}

couchdb.db.head() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "GET" "${host}/${db}" --head;
}

couchdb.db.purge() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "POST" "${host}/${db}/${couchdb[purge]}";
}

couchdb.db.commit() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "POST" "${host}/${db}/${couchdb[ensure.full.commit]}" \
    --header "Content-Type: ${mime_types[json]}";
}

couchdb.db.changes() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "GET" "${host}/${db}/${couchdb[changes]}";
}

couchdb.db.revslimit() {
  local host="${1:-}";
  local db="${2:-}";
  local amount="${3:-}";
  url encode "${db}" "db";
  local url="${host}/${db}/${couchdb[revs.limit]}";
  if [[ "${amount}" =~ ^[0-9]+$ ]]; then
    couchdb.run "PUT" "${url}" "-d" "${amount}";
  else
    couchdb.run "GET" "${url}";
  fi
}

couchdb.db.add() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "PUT" "${host}/${db}";
}

couchdb.db.rm() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "DELETE" "${host}/${db}";
}

couchdb.db.compact() {
  local host="${1:-}";
  local db="${2:-}";
  local view="${3:-}";
  url encode "${db}" "db";
  local url="${host}/${db}/${couchdb[compact]}";
  if [ -n "${view}" ]; then
    url encode "${view}" "view";
    url+="/${view}";
  fi
  couchdb.run "POST" "${url}" \
    --header "Content-Type: ${mime_types[json]}";
}

couchdb.db.cleanup() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "POST" "${host}/${db}/${couchdb[view.cleanup]}" \
    --header "Content-Type: ${mime_types[json]}";
}

couchdb.db.info() {
  local host="${1:-}";
  local db="${2:-}";
  url encode "${db}" "db";
  couchdb.run "GET" "${host}/${db}";
}

couchdb.db.alldocs() {
  local host="${1:-}";
  local db="${2:-}";
  local querystring="${3:-}";
  url encode "${db}" "db";
  local url="${host}/${db}/${couchdb[all.docs]}"
  if [ -n "$querystring" ]; then
    couchdb.querystring;
    url+="${querystring}";
  fi
  couchdb.run "GET" "${url}";
}

couchdb.doc.get() {
  local host="${1:-}";
  local db="${2:-}";
  local id="${3:-}";
  local querystring="";
  url query querystring "${@:4}";
  url encode "${db}" "db";
  url encode "${id}" "id";
  local url="${host}/${db}/${id}";
  if [ -n "${querystring}" ]; then
    couchdb.querystring;
    url+="${querystring}"
  fi
  couchdb.run "GET" "${url}" \
    --header "Accept: ${mime_types[json]}";
}

couchdb.doc.rm() {
  local host="${1:-}";
  local db="${2:-}";
  local id="${3:-}";
  local rev="${4:-}";
  url encode "${db}" "db";
  url encode "${id}" "id";
  url encode "${rev}" "rev";
  local url="${host}/${db}/${id}?rev=${rev}";
  couchdb.run "DELETE" "${url}";
}

couchdb.doc.head() {
  local host="${1:-}";
  local db="${2:-}";
  local id="${3:-}";
  url encode "${db}" "db";
  url encode "${id}" "id";
  local url="${host}/${db}/${id}";
  couchdb.run "GET" "${url}" --head;
}

couchdb.doc.save() {
  local host="${1:-}";
  local db="${2:-}";
  local doc="${3:-}";
  local id="${4:-}";
  url encode "${db}" "db";
  url encode "${id}" "id";
  if [ -f "${doc}" ]; then
    local url="${host}/${db}";
    local method="PUT";
    if [ -n "${id}" ]; then
      url+="/${id}";
    fi
    couchdb.run "${method}" "${url}" \
      --progress-bar --data-binary "@${doc}" \
      --header "Content-Type: ${mime_types[json]}";
  fi
}

couchdb.doc.copy() {
  local host="${1:-}";
  local db="${2:-}";
  local sourceid="${3:-}";
  local targetid="${4:-}";
  local rev="${5:-}";
  url encode "${db}" "db";
  url encode "${sourceid}" "sourceid";
  local url="${host}/${db}/${sourceid}";
  # NOTE: we do not need to url encode the
  # NOTE: target id or revision as they are sent
  # NOTE: as a header value
  local destination="${targetid}";
  if [ -n "${rev}" ]; then
    destination+="?rev=${rev}";
  fi
  couchdb.run "COPY" "${url}" --header "Destination: ${destination}";
}

couchdb.bulk() {
  local host="${1:-}";
  local db="${2:-}";
  local file="${3:-}";
  url encode "${db}" "db";
  if [ -f "${file}" ]; then
    local url="${host}/${db}/${couchdb[bulk.docs]}";
    couchdb.run "POST" "${url}" \
      --progress-bar --data-binary "@${file}" \
      --header "Content-Type: ${mime_types[json]}";
  fi
}

# send an attachment
couchdb.attach() {
  local host="${1:-}";
  local db="${2:-}";
  local id="${3:-}";
  local rev="${4:-}";
  local file="${5:-}";
  local name="${6:-}";
  local mime="${7:-}";
  local defaultmime="${8:-application/octet-stream}";
  if [ -f "${file}" ]; then
    if [ -z "${name}" ]; then
      fs.basename "${file}" "name";
    fi
    url encode "${db}" "db";
    url encode "${id}" "id";
    url encode "${name}" "name";
    url encode "${rev}" "rev";
    if [ -z "${mime}" ]; then
      mime=$( file -b --mime "${file}" || echo "" );
    fi
    if [ -z "${mime}" ]; then
      mime="${defaultmime}";
    fi
    local url="${host}/${db}/${id}/${name}?rev=${rev}";
    couchdb.run "PUT" "${url}" \
      --progress-bar --data-binary "@${file}" \
      --header "Content-Type: ${mime}";
  fi
}

# delete an attachment
couchdb.attach.rm() {
  local host="${1:-}";
  local db="${2:-}";
  local id="${3:-}";
  local rev="${4:-}";
  local name="${5:-}";
  url encode "${db}" "db";
  url encode "${id}" "id";
  url encode "${rev}" "rev";
  url encode "${name}" "name";
  local url="${host}/${db}/${id}/${name}?rev=${rev}";
  couchdb.run "DELETE" "${url}";
}

# download an attachment
couchdb.attach.get() {
  local host="${1:-}";
  local db="${2:-}";
  local id="${3:-}";
  local rev="${4:-}";
  local name="${5:-}";
  local file="${6:-}";
  url encode "${db}" "db";
  url encode "${id}" "id";
  url encode "${rev}" "rev";
  url encode "${name}" "name";
  local url="${host}/${db}/${id}/${name}?rev=${rev}";
  couchdb.run "GET" "${url}" --progress-bar \
    --output "${file}";
}

# info for a view
couchdb.view.info() {
  local host="${1:-}";
  local db="${2:-}";
  local view="${3:-}";
  url encode "${db}" "db";
  url encode "${view}" "view";
  local url="${host}/${db}";
  url+="/${couchdb[design]}/${view}/${couchdb[info]}";
  couchdb.run "GET" "${url}";
}

# query a view document
couchdb.view() {
  local host="${1:-}";
  local db="${2:-}";
  local design="${3:-}";
  local view="${4:-}";
  local querystring="${5:-}";
  url encode "${db}" "db";
  url encode "${design}" "design";
  url encode "${view}" "view";
  local url="${host}/${db}";
  url+="/${couchdb[design]}/${design}/${couchdb[view]}/${view}";
  if [ -n "$querystring" ]; then
    couchdb.querystring;
    url+="${querystring}";
  fi
  couchdb.run "GET" "${url}";
}

couchdb.querystring() {
  if [ -n "${querystring:-}" ] \
    && [[ ! "${querystring}" =~ ^\? ]]; then
      querystring="?${querystring}";
  fi
}
