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

query() {
  rlx.commands.query "$@";
}

# main
rlx.commands.design() {
  local template="query/alldocs";
  if [ $# -eq 0 ]; then
    console error -- "no design command specified";
  elif [ -z "${info[server]:-}" ]; then
    console error -- "no server selected";
  elif [ -z "${info[database]:-}" ]; then
    console error -- "no database selected";
  else
    local server="${info[server]}";
    local db="${info[database]}";
    local namespace="design.commands";
    local designlang="javascript";
    local designpath="${directories[template]}/design";
    local extension="${query_languages[$designlang]}";
    declare -A design_files;
    design_files[validate]="validate.${extension}";
    design_files[map]="map.${extension}";
    design_files[reduce]="reduce.${extension}";
    design_files[list]="list.${extension}";
    design_files[show]="show.${extension}";
    design_files[filter]="filter.${extension}";
    design_files[update]="update.${extension}";
    declare -A design_folders;
    design_folders[shows]="${design_files[show]}";
    design_folders[lists]="${design_files[list]}";
    design_folders[filters]="${design_files[filter]}";
    design_folders[updates]="${design_files[update]}";
    design_folders[views]="${design_files[map]}";
    local cmd="${1:-}"; shift;
    if ! method.exists? "${namespace}.${cmd}"; then
      console error -- "unknown design command %s" "${cmd}";
    else
      delegate "${namespace}" "${cmd}" "$@";
    fi
  fi
}

design.commands.ls() {
  local design="${1:-}";
  local parent file files=();
  validation.hostname || return 1;
  design.path;
  local IFS=$'\n';
  if [ -z "${design}" ]; then
    files=( $( ls "${parent}" ) );
  else
    if validation.design && validation.design.exists; then
      files=( $( find "${file}" ) );
    fi
  fi
  if [ ${#files[@]} -gt 0 ]; then
    for file in "${files[@]}"
      do
        echo "${file}";
    done
  fi
  unset IFS;
}

design.commands.rm() {
  local design="${1:-}";
  local name="${2:-}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists; then
    design.path;
    accepted() {
      rm -rf "${file}" > /dev/null 2>&1 \
        && console ok -- "deleted %s" "${file}" \
        || console error -- "error deleting %s" "${file}";
      rlx.prompt;
    }
    if [ -z "${name}" ]; then
      console prompt --program \
        "delete %s? (y/n)" "${file}";
      prompt confirm \
        --accepted=accepted \
        --rejected=rlx.rejected \
        --id=delete;
    else
      # TODO: get extension based on language for design documents when impl
      local opts=(-name "${name}" -o -name "${name}.${extension}");
      local files=( $( find "${file}" ${opts[@]} ) );
      if [ ${#files[@]} -eq 0 ]; then
        console error -- "no design document files found matching %s" \
          "${name}";
        return 1;
      elif [ ${#files[@]} -eq 1 ]; then
        file="${files[0]}";
        console prompt --program \
          "delete %s? (y/n)" "${file}";
        prompt confirm \
          --accepted=accepted \
          --rejected=rlx.rejected \
          --id=delete;
      else
        local IFS=$'\n';
        declare -A matches;
        local f path;
        for f in "${files[@]}"
          do
            path="${f#${parent}/}";
            matches[$path]="$f";
        done
        unset IFS;
        selected() {
          local word="${1:-}";
          if [ "${word}" == none ]; then
            return 0;
          fi
          local file="${matches[$word]:-}";
          if [ -n "${file}" ]; then
            accepted;
          fi
        }
        declare -ag PROMPT_SELECTION=( "${!matches[@]}" none );
        console prompt --program "select a file to delete";
        rlx.prompt.select;
      fi
    fi
  fi
}

design.commands.info() {
  local view="${1:-}";
  if validation.view; then
    rlx.run couchdb.view.info \
      "${server}" "${db}" "${view}";
    if ${flags[success]}; then
      rlx.json.print;
    fi
  fi
}

design.commands.compact() {
  local view="${1:-}";
  if validation.view; then
    rlx.run couchdb.db.compact \
      "${server}" "${db}" "${view}";
    if ${flags[success]}; then
      console info -- "compaction started on %s/%s" "${db}" "${view}";
    fi
  fi
}

design.commands.add() {
  local design="${1:-}";
  local parent file;
  if validation.design && validation.hostname; then
    design.path;
    parent="${file}";
    design.mkdir || return 1;
    local subfolders=( ${!design_folders[@]} );
    local folder="${parent}";
    local k;
    for k in "${subfolders[@]}"
      do
        parent="${folder}/${k}";
        design.mkdir || return 1;
    done
    console info -- "design document %s" "${file}";
  fi
}

design.commands.print() {
  local design="${1:-}";
  local print="${print:-true}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists; then
    local template="design";
    tpl clean;
    local validate="${file}/${design_files[validate]}";
    if [ -f "${validate}" ]; then
      export tpl_validate=$( cat "${validate}" );
    fi
    export tpl_language="${designlang}";
    design.exports ${!design_folders[@]};
    tpl parse "${template}" false false;
    if $print; then
      rlx.json.print "${files[input]}";
    fi
  fi
}

design.commands.validate() {
  local design="${1:-}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists; then
    local source="${designpath}/${designlang}/${design_files[validate]}";
    local target="${file}/${design_files[validate]}";
    if [ -f "${target}" ]; then
      source="${target}";
    fi
    saved() {
      document.saved "$@";
      rlx.prompt;
    }
    rlx.edit "${source}" "${target}" true;
  fi
}

design.commands.show() {
  local design="${1:-}";
  local name="${2:-}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists \
    && validation.name; then
    design.group shows show;
  fi
}

design.commands.filter() {
  local design="${1:-}";
  local name="${2:-}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists \
    && validation.name; then
    design.group filters filter;
  fi
}

design.commands.list() {
  local design="${1:-}";
  local name="${2:-}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists \
    && validation.name; then
    design.group lists list;
  fi
}

design.commands.update() {
  local design="${1:-}";
  local name="${2:-}";
  local parent file;
  if validation.design && validation.hostname \
    && validation.design.exists \
    && validation.name; then
    design.group updates update;
  fi
}

## QUERY

rlx.commands.query() {
  local template="query/custom";
  if [ $# -eq 0 ]; then
    console error -- "no query command specified";
  elif [ -z "${info[server]:-}" ]; then
    console error -- "no server selected";
  elif [ -z "${info[database]:-}" ]; then
    console error -- "no database selected";
  else
    local server="${info[server]}";
    local db="${info[database]}";
    local hostname;
    local namespace="query.commands";
    local cmd="${1:-}"; shift;
    if ! method.exists? "${namespace}.${cmd}"; then
      console error -- "unknown query command %s" "${cmd}";
    else
      delegate "${namespace}" "${cmd}" "$@";
    fi
  fi
}

query.commands.add() {
  local name="${1:-}";
  if validation.name && validation.hostname; then
    local include_docs=true;
    local reduce=false;
    local group=false;
    local key="";
    design.query.parse false false;
    local parent file;
    query.path;
    if [ -f "${file}" ]; then
      query edit "${name}";
      return 0;
    fi
    design.mkdir || return 1;
    saved() {
      document.saved "$@";
    }
    rlx.edit "${files[input]}" "${file}" true;
  fi
}

query.commands.edit() {
  local name="${1:-}";
  local parent file;
  if validation.name && validation.hostname \
    && validation.query.exists; then
    saved() {
      document.saved "$@";
    }
    rlx.edit "${file}" "${file}" true;
  fi
}

query.commands.rm() {
  local name="${1:-}";
  local parent file;
  if validation.name && validation.hostname \
    && validation.query.exists; then
    accepted() {
      rm "${file}" 2>/dev/null \
        && {
          console ok -- "deleted query %s" "${file}";
        } \
        || {
          console error -- "could not delete query %s" "${file}";
        }
      rlx.prompt;
    }
    console prompt --program \
      "delete query %s? (y/n)" "${name}";
    prompt confirm \
      --accepted=accepted \
      --rejected=rlx.rejected \
      --id=delete;
  fi
}

query.commands.ls() {
  local parent file;
  if validation.hostname; then
    query.path;
    if [ -d "${parent}" ]; then
      local nm;
      local queries=( $( find "${parent}" -name "*.json" ) );
      if [ ${#queries[@]} -gt 0 ]; then
        for file in "${queries[@]}"
          do
            fs.basename "${file}" "nm";
            nm="${nm%.${defaults[extension]}}";
            console print -- "$nm %s" "${file}";
        done
      fi
    fi
  fi
}

query.commands.all() {
  local name="${1:-}";
  local parent file;
  if validation.name && validation.hostname \
    && validation.query.exists; then
    local querystring;
    design.querystring "${file}";
    rlx.run couchdb.db.alldocs \
      "${server}" "${db}" "${querystring}";
    if ${flags[success]}; then
      rlx.json.paginate \
        "${files[response]:-}" "query all $*";
    fi
  fi
}

query.commands.run() {
  local name="${1:-}";
  local design="${2:-}";
  local view="${3:-}";
  local parent file;
  if validation.name && validation.hostname \
    && validation.query.exists \
    && validation.design && validation.view; then
    local querystring;
    design.querystring "${file}";
    rlx.run couchdb.view \
      "${server}" "${db}" "${design}" "${view}" "${querystring}";
    if ${flags[success]}; then
      rlx.json.paginate \
        "${files[response]:-}" "query run $*";
    fi
  fi
}

## PRIVATE

design.exports() {
  local k path methods;
  for k in $@
    do
      path="${file}/${k}";
      if [ -d "${path}" ]; then
        #echo "found key ($k) for path $path";
        methods=( $( find "${path}" -name "*.${extension}" ) );
        if [ ${#methods[*]} -gt 0 ]; then
          local IFS=$'\n';
          design.exports.files $k "${methods[@]}";
          unset IFS;
        fi
      fi
  done
}

design.exports.files() {
  local key="$1"; shift;
  local files=( $@ );
  local ids=();
  local file nm;
  for file in "${files[@]}"
    do
      fs.basename "${file}" nm;
      nm="${nm%.$extension}";
      ids+=( "$nm" );
      export "tpl_${key}_${nm}"="$( cat "${file}" )";
  done
  tpl array $key "${ids[@]}";
  #tpl print;
}

design.mkdir() {
  if [ ! -d "${parent}" ]; then
    mkdir -p "${parent}" \
      || {
        console error -- "could not create %s" "${parent}";
        return 1;
      }
  fi
  return 0;
}

query.path() {
  local filename="${name:-}.${defaults[extension]}";
  parent="${directories[query]}";
  parent+="/${hostname}/${db}";
  file="${parent}/${filename}";
}

design.path() {
  local filename="${design:-}";
  parent="${directories[design]}";
  parent+="/${hostname}/${db}";
  file="${parent}/${filename}";
}

design.query.parse() {
  local print="${1:-false}";
  local plain="${2:-}";
  local limit="${limit:-${rlx_settings[limit]:-10}}";
  local descending="${descending:-${rlx_settings[descending]:-false}}";
  local include_docs="${include_docs:-false}";
  local inclusive_end="${inclusive_end:-true}";
  local skip="${skip:-0}";
  tpl clean;
  if [ -n "${startkey:-}" ]; then
    tpl set "startkey=${startkey}";
  fi
  if [ -n "${endkey:-}" ]; then
    tpl set "endkey=${endkey}";
  fi
  if [ -n "${keys:-}" ]; then
    tpl set "keys=${keys}";
  fi
  if [ -n "${key:-}" ]; then
    tpl set "key=${key}";
  fi
  if [ -n "${reduce:-}" ]; then
    tpl set "reduce=${reduce}";
  fi
  if [ -n "${group:-}" ]; then
    tpl set "group=${group}";
  fi
  rlx.debug "parse query %s (print=%s,plain=%s)" \
    "${template}" "${print}" "${plain}";
  #echo "parsing with page limit ${rlx_settings[limit]}"
  tpl parse "${template}" $print $plain \
    "limit=${limit}" \
    "include_docs=${include_docs}" \
    "inclusive_end=${inclusive_end}" \
    "skip=${skip}" \
    "descending=${descending}";
}

design.alldocs.create() {
  local template="query/alldocs";
  design.query.parse false false;
}

## QUERY STRING

design.alldocs.querystring() {
  design.alldocs.create;
  design.querystring "${files[input]}";
}

design.querystring() {
  local file="${1:-}";
  local print="${2:-false}";
  if [ ! -f "${file}" ]; then
    console error -- "query string file %s does not exist" "${file}";
    return 1;
  fi
  local interpreter;
  local lang="${rlx_settings[templatelang]:-${rlx_settings[lang]:-}}";
  rlx.interpreter querystring interpreter "${lang}";
  querystring=$( "$interpreter" < "${file}" );
  if [[ "${querystring}" =~ ${regexp[limit]} ]]; then
    local limit="${BASH_REMATCH[2]}";
    limit=$((limit-1));
    # update the setting
    settings set limit "$limit";
  else
    #echo "querystring does not contain limit...";
    local limit="${rlx_settings[limit]:-10}";
    limit=$((limit+1));
    querystring+="&limit=${limit}";
  fi
}

design.group() {
  local group="${1:-}";
  local key="${2:-}";
  file="${file}/${group}";
  # NOTE: entire group may have been deleted with rm command
  if [ ! -d "${file}" ]; then
    mkdir -p "${file}" \
      || {
        console error -- "could not create %s" "${file}";
        return 1;
      }
  fi
  local source="${designpath}/${designlang}/${design_files[$key]}";
  local target="${file}/${name}.${extension}";
  if [ -f "${target}" ]; then
    source="${target}";
  fi
  saved() {
    document.saved "$@";
    rlx.prompt;
  }
  rlx.edit "${source}" "${target}" true;
}

## VALIDATION

validation.design() {
  if [ -z "${design:-}" ]; then
    console error -- "no design document specified";
    return 1;
  fi
  return 0;
}

validation.view() {
  if [ -z "${view:-}" ]; then
    console error -- "no view specified";
    return 1;
  fi
  return 0;
}

validation.name() {
  if [ -z "${name:-}" ]; then
    console error -- "no name specified";
    return 1;
  elif [[ ! "${name:-}" =~ ${regexp[name]} ]]; then
    console error -- "invalid name %s" "${name:-}";
  fi 
  return 0;
}

validation.hostname() {
  if [[ "${server:-}" =~ ${regexp[hostname]} ]]; then
    hostname="${BASH_REMATCH[2]}";
    return 0;
  else
    console error -- "could not extract hostname from %s" "${server:-}";
  fi
  return 1;
}

validation.query.exists() {
  query.path;
  if [ ! -f "${file}" ]; then
    console error -- "query %s does not exist" "${file}";
    return 1;
  fi
  return 0;
}

validation.design.exists() {
  design.path;
  if [ ! -d "${file}" ]; then
    console error -- "design document %s does not exist" "${file}";
    return 1;
  fi
  return 0;
}
