#!/usr/bin/env bash

cbl_error() {
  printf '[CBL][ERROR] %s\n' "$*" >&2
}

cbl_read_param_value_from_file() {
  local param_file="$1"
  local key="$2"
  local line=""
  local cur_key=""
  local cur_value=""

  if [ ! -f "$param_file" ]; then
    return 1
  fi

  while IFS= read -r line || [ -n "$line" ]; do
    line=${line%$'\r'}
    case "$line" in
      ''|'#'*)
        continue
        ;;
    esac

    cur_key=${line%%=*}
    cur_value=${line#*=}
    if [ "$cur_key" = "$key" ]; then
      cur_value=${cur_value//\\n/$'\n'}
      cur_value=${cur_value//\\r/$'\r'}
      printf '%s\n' "$cur_value"
      return 0
    fi
  done < "$param_file"

  return 1
}

cbl_key_to_var_name() {
  local key="$1"
  key=$(printf '%s' "$key" | tr '[:upper:]' '[:lower:]')
  key=$(printf '%s' "$key" | sed 's/[^a-z0-9_]/_/g')
  printf '%s\n' "$key"
}

cbl_load_param_file() {
  local param_file="$1"
  local line=""
  local key=""
  local value=""
  local var_name=""

  if [ -z "$param_file" ]; then
    cbl_error "empty --param-file path"
    return 1
  fi
  if [ ! -f "$param_file" ]; then
    cbl_error "param file not found: $param_file"
    return 1
  fi

  printf -v param_file '%s' "$param_file"
  export param_file

  while IFS= read -r line || [ -n "$line" ]; do
    line=${line%$'\r'}
    case "$line" in
      ''|'#'*)
        continue
        ;;
    esac

    key=${line%%=*}
    value=${line#*=}
    value=${value//\\n/$'\n'}
    value=${value//\\r/$'\r'}

    var_name=$(cbl_key_to_var_name "$key")
    if [[ ! "$var_name" =~ ^[a-z_][a-z0-9_]*$ ]]; then
      cbl_error "invalid parameter key: $key"
      return 1
    fi

    printf -v "$var_name" '%s' "$value"
    export "$var_name"
  done < "$param_file"

  return 0
}

init_colors() {
  if [ "${rcl_color:-1}" = "0" ] || [ -n "${no_color:-${NO_COLOR:-}}" ]; then
    red=""
    green=""
    yellow=""
    blue=""
    cyan=""
    magenta=""
    bold=""
    reset=""
  else
    red=$'\033[31m'
    green=$'\033[32m'
    yellow=$'\033[33m'
    blue=$'\033[34m'
    cyan=$'\033[36m'
    magenta=$'\033[35m'
    bold=$'\033[1m'
    reset=$'\033[0m'
  fi

  return 0
}

log_info() {
  printf '%b\n' "${cyan}[INFO]${reset} $*"
  return 0
}

log_warn() {
  printf '%b\n' "${yellow}${bold}[WARN]${reset} ${yellow}$*${reset}"
  return 0
}

log_error() {
  printf '%b\n' "${red}${bold}[ERROR]${reset} ${red}${bold}$*${reset}"
  return 0
}

log_ok() {
  printf '%b\n' "${green}[OK]${reset} $*"
  return 0
}

log_skip() {
  printf '%b\n' "${blue}[SKIP]${reset} $*"
  return 0
}

cbl_split_command_for_log() {
  local cmd="$1"
  local len=${#cmd}
  local i=0
  local ch=""
  local token=""
  local in_single=0
  local in_double=0
  local escaped=0

  rcl_cmd_log_tokens=()

  for ((i = 0; i < len; i++)); do
    ch="${cmd:i:1}"

    if [ "$escaped" -eq 1 ]; then
      token+="$ch"
      escaped=0
      continue
    fi

    if [ "$ch" = "\\" ]; then
      token+="$ch"
      escaped=1
      continue
    fi

    if [ "$ch" = "'" ] && [ "$in_double" -eq 0 ]; then
      token+="$ch"
      if [ "$in_single" -eq 1 ]; then
        in_single=0
      else
        in_single=1
      fi
      continue
    fi

    if [ "$ch" = '"' ] && [ "$in_single" -eq 0 ]; then
      token+="$ch"
      if [ "$in_double" -eq 1 ]; then
        in_double=0
      else
        in_double=1
      fi
      continue
    fi

    if [[ "$ch" =~ [[:space:]] ]] && [ "$in_single" -eq 0 ] && [ "$in_double" -eq 0 ]; then
      if [ -n "$token" ]; then
        rcl_cmd_log_tokens+=("$token")
        token=""
      fi
      continue
    fi

    token+="$ch"
  done

  if [ -n "$token" ]; then
    rcl_cmd_log_tokens+=("$token")
  fi

  return 0
}

cbl_cmd_token_takes_value_on_same_line() {
  local token="$1"

  case "$token" in
    -G|-S|-B|-A|-T|-C|-D|-U|-I|-L|-o|-c|-l|--build|--install|--prefix|--parallel|--target|--config|--preset|--fresh)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

log_cmd() {
  local cmd="$*"
  local i=0
  local count=0
  local line=""

  if [ "${rcl_cmd_multiline:-1}" = "0" ]; then
    printf '%b\n' "${magenta}[CMD]${reset} $cmd"
    return 0
  fi

  cbl_split_command_for_log "$cmd"
  count=${#rcl_cmd_log_tokens[@]}

  if [ "$count" -eq 0 ]; then
    printf '%b\n' "${magenta}[CMD]${reset}"
    return 0
  fi

  printf '%b\n' "${magenta}[CMD]${reset}"

  for ((i = 0; i < count; i++)); do
    line="${rcl_cmd_log_tokens[$i]}"

    if [ "$i" -lt $((count - 1)) ] && cbl_cmd_token_takes_value_on_same_line "$line"; then
      line="$line ${rcl_cmd_log_tokens[$((i + 1))]}"
      i=$((i + 1))
    fi

    if [ "$i" -lt $((count - 1)) ]; then
      printf '  %s %s\n' "$line" "\\"
    else
      printf '  %s\n' "$line"
    fi
  done

  return 0
}

log_kv() {
  local key="$1"
  local value="$2"

  printf '%b\n' "${cyan}[$key]${reset}:$value"
  return 0
}

rcl_is_recursive_action_name() {
  local action_name="${1:-}"

  case "$action_name" in
    *_recursive)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

rcl_recursive_base_action_name() {
  local action_name="${1:-}"

  printf '%s\n' "${action_name%_recursive}"
  return 0
}

rcl_is_single_library_action_name() {
  local action_name="${1:-}"

  case "$action_name" in
    clean_src|src|configure|clean_build|build|clean_install|install|clean_build_install|clean_build_and_install|auto)
      return 0
      ;;
    *)
      return 1
      ;;
  esac
}

rcl_log_guess_current_id() {
  local id=""

  if [ -n "${rcl_id:-}" ]; then
    printf '%s\n' "$rcl_id"
    return 0
  fi

  if [ -n "${rcl_current_id:-}" ]; then
    printf '%s\n' "$rcl_current_id"
    return 0
  fi

  if [ -n "${rcl_config_file:-}" ] && [ -f "${rcl_config_file:-}" ]; then
    id=$(rcl_read_config_value_from_file "$rcl_config_file" "rcl_id" 2>/dev/null || true)
    if [ -n "$id" ]; then
      printf '%s\n' "$id"
      return 0
    fi
  fi

  if [ -n "${rcl_install_dir:-}" ]; then
    id=$(rcl_find_id_by_install_dir 2>/dev/null || true)
    if [ -n "$id" ]; then
      printf '%s\n' "$id"
      return 0
    fi
  fi

  if [ -n "${rcl_install_dir:-}" ]; then
    basename "$rcl_install_dir"
    return 0
  fi

  printf '%s\n' "unknown"
  return 0
}

rcl_log_target_name_for_action() {
  local action_name="${1:-}"
  local id="${2:-unknown}"
  local base_action=""

  if [ -z "$action_name" ]; then
    printf '%s\n' "unknown"
    return 0
  fi

  if rcl_is_recursive_action_name "$action_name"; then
    base_action=$(rcl_recursive_base_action_name "$action_name")
    printf '%s\n' "recursive_${base_action}_${id}"
    return 0
  fi

  if rcl_is_single_library_action_name "$action_name"; then
    printf '%s\n' "single_${action_name}_${id}"
    return 0
  fi

  printf '%s\n' "${action_name}_${id}"
  return 0
}

rcl_log_context() {
  local current_id=""
  local current_action=""
  local current_step=""
  local big_action=""
  local root_id=""
  local big_target=""
  local current_target=""

  if [ -z "${action:-}" ]; then
    return 0
  fi

  current_id=$(rcl_log_guess_current_id)
  current_action="${rcl_current_action:-$action}"
  current_step="${rcl_current_step:-}"
  big_action="${rcl_big_action:-${rcl_root_action:-$action}}"
  root_id="${rcl_recursive_root_id:-${rcl_root_id:-$current_id}}"

  if [ -z "$current_step" ]; then
    current_step="$current_action"
  fi

  big_target=$(rcl_log_target_name_for_action "$big_action" "$root_id")
  current_target=$(rcl_log_target_name_for_action "$current_action" "$current_id")

  printf '%b\n' "${cyan}[big_target]${reset}:$big_target"
  printf '%b\n' "${cyan}[big_action]${reset}:$big_action"
  printf '%b\n' "${cyan}[root_id]${reset}:$root_id"
  printf '%b\n' "${cyan}[current_target]${reset}:$current_target"
  printf '%b\n' "${cyan}[current_action]${reset}:$current_action"
  printf '%b\n' "${cyan}[current_id]${reset}:$current_id"
  printf '%b\n' "${cyan}[current_step]${reset}:$current_step"

  if [ -n "${rcl_parent_action:-}" ]; then
    printf '%b\n' "${cyan}[parent_action]${reset}:$rcl_parent_action"
  fi

  if [ -n "${rcl_parent_id:-}" ]; then
    printf '%b\n' "${cyan}[parent_id]${reset}:$rcl_parent_id"
  fi

  return 0
}

log_stage() {
  local title="$*"

  echo
  printf '%b\n' "${red}${bold}============================================================${reset}"
  printf '%b\n' "${red}${bold}STAGE:${reset} ${red}${bold}${title}${reset}"
  rcl_log_context
  printf '%b\n' "${red}${bold}============================================================${reset}"
  return 0
}

rcl_set_current_step() {
  rcl_current_step="$1"
  return 0
}

rcl_action_stage() {
  local action_name="$1"
  local step_name="${2:-$1}"

  rcl_current_action="$action_name"
  rcl_current_step="$step_name"
  log_stage "action $action_name"
  return 0
}

rcl_step_stage() {
  local step_name="$1"

  rcl_current_step="$step_name"
  log_stage "$step_name"
  return 0
}

on_error() {
  local exit_code="$?"
  local line_no="${1:-unknown}"
  local failed_cmd="${2:-unknown}"

  log_error "FAILED"
  log_error "exit_code:$exit_code"
  log_error "line:$line_no"
  log_error "command:$failed_cmd"

  if [ -n "${action:-}" ]; then
    log_error "big_action:${rcl_big_action:-${rcl_root_action:-$action}}"
    log_error "current_action:${rcl_current_action:-$action}"
    log_error "current_step:${rcl_current_step:-unknown}"
    log_error "current_id:$(rcl_log_guess_current_id 2>/dev/null || printf '%s' unknown)"
  fi

  exit "$exit_code"
}

cbl_apply_param_file_defaults() {
  rcl_cmake_cmd="${rcl_cmake_cmd:-}"
  rcl_cmake_cmd=${rcl_cmake_cmd//\\;/;}

  if [ -z "${rcl_log_path:-}" ] && [ -n "${rcl_log_dir:-}" ] && [ -n "${action:-}" ]; then
    rcl_log_path="$rcl_log_dir/${action}_out.log"
  fi

  return 0
}

cbl_parse_param_file_args() {
  if [ "$#" -lt 3 ]; then
    log_error "usage: $0 --param-file <file> <action>"
    exit 1
  fi

  param_file="$2"
  cbl_load_param_file "$param_file"
  shift 2

  action="$1"
  shift

  cbl_apply_param_file_defaults
  return 0
}

cbl_parse_legacy_register_args() {
  if [ "$#" -lt 14 ]; then
    log_error "not enough arguments"
    log_error "expected 14, got $#"
    echo "usage:"
    echo "  $0 action rcl_git_url cmake_build_type cmake_command cmake_c_compiler cmake_cxx_compiler cmake_generator cmake_toolchain_file rcl_source_dir rcl_build_dir rcl_install_dir rcl_download_cmd rcl_cmake_cmd rcl_log_path"
    exit 1
  fi

  action=$1
  shift
  rcl_git_url=$1
  shift
  cmake_build_type=$1
  shift
  cmake_command=$1
  shift
  cmake_c_compiler=$1
  shift
  cmake_cxx_compiler=$1
  shift
  cmake_generator=$1
  shift
  cmake_toolchain_file=$1
  shift
  rcl_source_dir=$1
  shift
  rcl_build_dir=$1
  shift
  rcl_install_dir=$1
  shift
  rcl_download_cmd=$1
  shift
  rcl_cmake_cmd=$1
  shift
  rcl_cmake_cmd=${rcl_cmake_cmd//\\;/;}
  rcl_log_path=$1
  shift

  return 0
}

cbl_parse_args() {
  case "${1:-}" in
  --param-file)
    cbl_parse_param_file_args "$@"
    ;;
  list_actions)
    action="list_actions"
    ;;
  help|-h|--help)
    action="help"
    ;;
  *)
    cbl_parse_legacy_register_args "$@"
    ;;
  esac

  return 0
}
