#!/usr/bin/env bash

# branch: feature/pathConfig
# echo "entering: ${BASH_SOURCE[0]}"
# echo "remove all echos to preseve output"
# echo ""

export PS4='\e[90m  ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO}   - ${BASH_SOURCE[@]##*/} \e[0m'
if [ -n "$SH_OPT" ]; then set $SH_OPT; fi # force debug


if [ -z "$LOG_LEVEL" ]; then
  export LOG_LEVEL=3
  export LOG_DEVICE=/dev/stdout
  #export LOG_DEVICE=/dev/tty
fi


if [ -z "$EXPECTED_RETURN_VALUE" ]; then
  export EXPECTED_RETURN_VALUE=1
fi

if [ "$EXPECTED_RETURN_VALUE" -lt 1 ]; then
  export EXPECTED_RETURN_VALUE=1
fi


if [ -z "$OOSH_DIR" ]; then
  OOSH_DIR=$( cd "$(dirname ${BASH_SOURCE[0]})" ; pwd ) 
  if [ "$OOSH_DIR" = "." ]; then
    OOSH_DIR=$(pwd)
  fi
  if [ "$OOSH_DIR" = "$USERHOME/init" ]; then
    OOSH_DIR="$USERHOME/oosh"
  fi
  PATH="$OOSH_DIR:$PATH"
fi

info.log() {
  if [ -n "$LOG_LIVE" ] \
  && [ "$LOG_LEVEL" -gt "1" ]; then
    echo -e "[90mINFO: ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO}   - ${BASH_SOURCE[@]##*/} >$*${NORMAL}"  >>$LOG_LIVE     
  fi
  if [ "$LOG_LEVEL" -gt "3" ]; then
    echo -e "\e[90mINFO> $*\e[0m" >>$LOG_DEVICE ## normal
  fi
}

this.isNumber() {
  case $1 in
  '' | *[!0-9]*) return 1 ;;
  *) return 0 ;;
  esac
}

this.functionExists() {

  local startFunc="$1"
  shift
  RETURN=$1

  if [ "$(type -t $startFunc)" = "function" ]; then
    info.log "$startFunc exists"
    return 0
  else
    info.log "$startFunc does not exist"
    return 1
  fi
} 2>/dev/null

if (this.functionExists this.load); then
  info.log "this was already sourced"
  return 0
fi

this.load() { # <aFunction> <aShellScript> # loads <aFunction> from a <aShellScript>
  local aFunction=$1
  shift
  if [ -n "$1" ]; then
    local fullQualifiedScript="$(which $1)"
    local aShellScript=$(basename $1)
    shift
  else
    error.log "this.load: No script name provided!"
    return 1
  fi

  if (this.functionExists $aShellScript.$aFunction); then
    info.log "$aShellScript was already sourced"
  else
    if [ -z "$fullQualifiedScript" ]; then
      warn.log "Please check \$PATH: $PATH"
      fullQualifiedScript="$aShellScript"
    fi
    info.log "sourcing $fullQualifiedScript $aShellScript.$aFunction $@"
    source $fullQualifiedScript
    This=$fullQualifiedScript
  fi
  $aShellScript.$aFunction "$@"
  #stop.log "loaded and executed $aShellScript.$aFunction"
  RETURN=$1
  return "$(result save)"
}

check.debug.level() {
  if [ -n "$1" ]; then
      echo "LOF_LEVEL is: $LOG_LEVEL ....checking for level $1"
  #   log.level $1
  fi

  if [ "$LOG_LEVEL" -gt "6" ]; then
    info.log "step Debug ON: LOG LEVEL=$LOG_LEVEL"
    export STEP_DEBUG=ON
  else
    export STEP_DEBUG=OFF
  fi

  if [ "$LOG_LEVEL" -gt "5" ]; then
    export PS4='\e[90m  ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO}   - ${BASH_SOURCE[@]##*/} \e[0m'
    set -x
  else
    {
      # echo "should not debug $LOG_LEVEL ${BASH_SOURCE[0]##*/} -> ${BASH_SOURCE[1]##*/}: ${FUNCNAME[0]}:${LINENO}   - ${BASH_SOURCE[@]##*/}"
      #echo "check.debug.level $1=$LOG_LEVEL"
      set +x
    } 2>/dev/null
  fi
  #set -x
} # 2>/dev/null

if ! [ "$(type -t debug.v)" = "function" ]; then
  #LOG_LEVEL=6
  source $(dirname ${BASH_SOURCE[0]})/debug
fi

this.init() {
  if [ -n "$CONFIG" ]; then
    info.log "inititlized"
    if [ -f "$CONFIG" ]; then
      if ! (this.isSourced); then
        source "$CONFIG"
      fi
    fi
    return 0
  fi
  #set -x
  #does not work on mac osx. readlink does not have option -f on mac
  #local initStartPath=$(dirname $(readlink -f "${BASH_SOURCE[0]}"))
  local initStartPath=$(dirname "${BASH_SOURCE[0]}")
  let shellLevel=$SHLVL-1
  let ooShellLevel=$SHLVL+2
  #echo initStartPath $initStartPath

  # if (loop list PATH find $initStartPath); then
  #   loop list PATH rm $initStartPath
  # fi
  #export PATH=$initStartPath:$PATH

  #export PS1="[oosh $ONCE_SSH_CONFIG_HOST] $PS1"
  if ! [ "${0##*/}" = "config" ]; then
    # if [ -z "$CONFIG" ]; then
    #   echo "no config found...initialising it: $OOSH_DIR/../config"
    #   #source $OOSH_DIR/../config/user.env
    #   #config.save


    #   this.path.add "$OOSH_DIR"
    #   this.path.add "$OOSH_DIR/ng"
    #   this.path.add "$OOSH_DIR/external"
    #   this.path.add "$OOSH_DIR/init"
    #   if [ "$USER" = "root" ]; then
    #     this.path.add "$OOSH_DIR/su"
    #   fi
    #   this.path.add "."

    #   if [ -n "$OOSH_CONFIG_NEEDS_SAVE" ]; then
    #     unset OOSH_CONFIG_NEEDS_SAVE
    #     config.save
    #   fi
    #   hash -r
    #   echo "initialising config done: $CONFIG"
    # fi
    if [ -f "$CONFIG" ]; then
      source "$CONFIG"
    fi
  fi
  if [ -z "$OOSH_PROMPT" ]; then
    export OOSH_STATUS="0: started in shell level: $shellLevel"
    export OOSH_DIR="$initStartPath"
    export OOSH_SHLVL=$ooShellLevel
    export OOSH_PROMPT="oosh "
    export ERROR_CODE_RECONFIG=117
    export OOSH_CONFIG_NEEDS_SAVE=~/config/user.env
  fi
}

create.result() # <processRETURN_VALUE:0> <RESULT:all went well> <option:$1> # creates a process return value and a function RESULT that been be returned in the calling function
# to make create result work, you have to call a SOURCED function
# e.g.  if os.check some.function; then ...
#   if you call 'os check' from th scrip 'os', it cannot return a RESULT since the porcess will end and no RESULT is left...
#   if you call 'os.check' you have to source the script oo before: 
#       source oo
# Then $RESULT will be in the same process and can be read from the calling function
#   otherwise if you call 'os check' you have to call:
#      create.result ERROR_CODE "some RSULT value" save
# <option> can be 'save' then you can retrieve the result with
#      result.load
# in the calling function.
# normaly <option> should always be "$1" to mark the start of the next command
{

  RETURN_VALUE=$1
  shift
  RESULT=$1
  shift
  RETURN=$1
  

  if ! (this.isNumber "$RETURN_VALUE"); then
    export RESULT=$RETURN_VALUE
    RETURN_VALUE=0
  fi
  
  info.log "create.result: $RETURN_VALUE $RESULT"
  if [ "$RETURN" = "save" ]; then    
    shift
    RETURN=$1
    result.save "$@"
    info.log "result saved"
  fi
  info.log "next argument: $RETURN"
  
  # if ! (this.isSourced); then
  #   important.log "show RESULT: $RESULT"
  # fi
}

result() {
  if [ "$1" = "save" ]; then    
    shift
    result.save "$@"
    RETURN=$1
  fi
  if [ -z "$RETURN_VALUE" ]; then
    RETURN_VALUE=0
  fi 
  echo $RETURN_VALUE
}

result.save()
{
  export RESULT_RETURN_VALUE=$RETURN_VALUE
  export RESULT=$RESULT

  export RESULT_DATE="$(date)"
  export RESULT_CALLER="${FUNCNAME[2]}"
  export RESULT_CALL_STACK="${FUNCNAME[@]}"
  export RESULT_SOURCE_STACK="${BASH_SOURCE[@]##*/}"
  export RESULT_NEXT_ARGS="$*"
  {
    declare -px | grep "RESULT"
  } >$CONFIG_PATH/result.env
}

result.cat() { # # logs the $CONFIG_PATH/result.env file
  cat $CONFIG_PATH/result.env
}

result.show() { # # alias to result.cat
  result.cat
}

result.view() { # # alias to result.cat
  result.cat
}

result.load()
{
  source $CONFIG_PATH/result.env
}

result.into() { # <shellVarname> <?method> #
  export $1="$( cat - )"
  echo "$1=${!1}" >$CONFIG_PATH/result.env
  # if [ -n "$2" ]; then
  #   "$@"
  # fi
}

this.scope() {
  declare -p
  declare -f | grep "^[^ ]* ()" | cut -d ' ' -f1
}

this.scope.full() {
  declare -p
  this.scope
}

this.absolutePathName()     # <?path:"."> <?option:"pipe"> echos the absolut path if started else it just puts it into $RESTULT
{
  local file=$1
  shift

  if [ -z "$file" ]; then
    file="$0"
  fi

  debug.log "file: $file"
  local path=""
  local dir=""
  if [ -d "$file" ]; then
    dir="$file"
  else
    dir="$(dirname "$file")"
  fi
  debug.log "local dir: $dir"
  path=$(cd $dir; pwd) 
  local name="$(basename $file)"
    

  create.result 0 "$path/$name" "$1"


  if ! (this.isSourced); then
    if [ -n "$1" ]; then
      shift
      RETURN="$1"
      echo "$RESULT"
    else
      console.log "absolutePathName: $RESULT"
    fi
  fi
}

this.absolutePath()     # <?path:"."> <?option:"pipe"> echos the absolut path if started else it just puts it into $RESTULT
{
  local file=$1
  shift

  if [ -z "$file" ]; then
    file="$0"
  fi

  # resoleved_link=$(readlink ${file})
  # if [ -z "$resoleved_link" ]; then
  #   resoleved_link="$file"
  # fi
  # resoleved_link=$(readlink -f ${file})
  # if [ -z "$resoleved_link" ]; then
  #   resoleved_link="$file"
  # fi

  #create.result "$(dirname $(pwd)/${file#./})" "$1"
  debug.log "file: $file"
  local path=""
  local dir=""
  if [ -d "$file" ]; then
    dir="$file"
  else
    dir=$(dirname "$file")
  fi
  info.log "local dir: $dir"
  path=$( cd "$dir" ; pwd ) 
    

  create.result "$path" "$1"


  if ! (this.isSourced); then
    if [ "$1" = "pipe" ]; then
      shift
      RETURN="$1"
      echo "$RESULT"
    else
      console.log "absolutePath: $RESULT"
    fi
  # else
  #   if [ -n "$1" ]; then
  #         console.log "absolutePath: $RESULT"
  #   fi
  fi
}

this() {
  create.result "${0##*/}" "$1"
}

this.caller() {
  local len=${#BASH_LINENO[@]}
  local caller=${BASH_SOURCE[2]##*/}
  info.log "script: $0"
  info.log "caller: $caller"
  info.log "source stack: ${BASH_SOURCE[*]##*/}"
  create.result "$caller" "$1" 
}

this.caller.function() {
  local function=${FUNCNAME[1]}
  info.log "function stack: ${FUNCNAME[*]}"
  create.result "$function" "$1" 
}

this.call() # <aFunction> <?aShellScript> # loads <aFunction> from a <aShellScript>
{
  local aFunction=$1
  shift

  local len=${#BASH_LINENO[@]}
  local caller=${BASH_SOURCE[$len - 2]##*/}
  #info.log "stack : ${BASH_SOURCE[@]}"
  info.log "script: $0"
  info.log "caller: $caller"

  if (this.functionExists $aFunction); then
    debug.log "this.call: $aFunction $*"
    $aFunction "$@"
    return "$?"
  #else
    #info.log "$aFunction does not exist!"
  fi
  if (this.functionExists $caller.$aFunction); then
    debug.log "this.call: $caller.$aFunction $*"
    $caller.$aFunction "$@"
  else
    info.log "$caller.$aFunction does not exist!"
    local aShellScript=$aFunction
    aFunction=$1
    shift

    info.log "aShellScript: $aShellScript"
    info.log "aFunction: $aFunction"
    info.log "next parameter \$1: $1"

    # if [ -z "$aFunction" ]; then
    #   aFunction="$aShellScript"
    #   aShellScript=$(which $caller)
    # fi

    if (this.functionExists $aFunction); then
      debug.log "this.call: $aFunction $* "
      stop.log "$aFunction $*"
      $aFunction "$@"
    else
      if [ -z "$aFunction" ]; then
        aFunction=usage
      fi
      important.log "this.load: $aFunction $aShellScript"
      if (this.load $aFunction $aShellScript "$@"); then
        debug.log "done this.call: $aShellScript.$aFunction $*"
        #$aShellScript.$aFunction "$@"
      else
        problem.log "this.load faild to load $aShellScript from \"$aFunction\": $?"
        return 127
      fi
    fi
  fi
} #2>/dev/null



this.path.add() {

  # if ! this.functionExists "line.add"; then
  #   source line
  # fi

  #   echo $PATH \
  # | line.split ":" \
  # | line.quote \
  # | line.remove "$1" \
  # | line.add "$1" \
  # | line.join ":" \
  # | line.into PATH

  if ! this.functionExists "line.add"; then
    export PATH="$OOSH_DIR:$PATH"
    echo "PATH: $PATH"
    source $OOSH_DIR/line '' ''
    #set -x
  fi

  local currentPath=$1
  if [ "$currentPath" = "." ]; then
    important.log "fixing .     $OOSH_DIR    $1"
    currentPath="\."
    #set -x
  fi

    echo $PATH \
  | line.split ":" \
  | line.quote \
  | line.remove "$currentPath" \
  | line.prepend "$1" \
  | line.join ":" \
  | line.into PATH

  
  # source $CONFIG_PATH/result.env
  # shift

  RESULT=$PATH
  # if (loop.list PATH find "$1" silent); then
  #   debug.log "found $1 at $RESULT"
  #   loop.list PATH rm $1 silent
  #   debug.log "PATH=$1:$RESULT"
  # fi

  #export PATH="$1:$RESULT"
}


this.help.table() {
  #echo $@
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi
  shift
  cat "$from" \
  | line find "\(\) " "$" \
  | sed 's/() //'  \
  | sed 's/{//' \
  | sed "s/^$this.//" \
  | grep "$1" \
  | line table '#' 
}

this.help() {
  #echo $@
  source line
  #set -x
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    from="$This"
  fi
  shift

  cat "$from" \
  | line.find "\(\)" "\{" \
  | line.filter "^#" \
  | line.filter "^{" \
  | line.filter "\.completion" \
  | line.filter "^private" \
  | line.quote \
  | sort \
  | grep "$this\.$1" \
  | line.prepend "'=========== # =========== # ==========='" \
  | line.prepend "'METHOD # PARAMETER # DESCRIPTION'" \
  | line.unify '#' " no name" " ${GRAY}none" " ${GRAY}please add a description" \
  | line.trim.quoted \
  | line.replace "\([^ ]\)()" "\1" \
  | line.replace " {" \
  | line.replace "$this\." \
  | line.format FORMAT_HELP 
}



this.isSourced() {
  # test.isSourced this.isSourced \ this.isSourced main                                               => called by main            and started
  # init this.isSourced           \ this.isSourced main                                               => called by main            and started
  # this.call this.isSourced      \ this.isSourced this.call main                                     => called by this.call       and sourced
  # test.case source              \ this.isSourced this.start mycmd.start source test.case main       => called by this.start      and sourced from test.case
  # mycmd.feature3 hello world    \ this.isSourced mycmd.feature3 test.case main                      => called by mycmd.feature3

  caller=${BASH_SOURCE[1]##*/}
  callerFunction=${FUNCNAME[1]}

  case $callerFunction in
  "main") #started
    create.result 1 "started by $callerFunction in $caller"
    ;;
  "this.call") #called therfore sourced
    create.result 0 "sourced because called by $callerFunction in $caller"
    ;;
  "this.start") #started depending on command [3]
    if [ "${FUNCNAME[3]}" = "source" ]; then
      create.result 0 "sourced because called by $callerFunction in $caller" >/dev/null
    else
      create.result 1 "started by ${FUNCNAME[4]} in ${BASH_SOURCE[4]##*/}" >/dev/null
    fi
    ;;
  *) #called therfore sourced
    if [ "${0##*/}" = "$caller" ]; then
      create.result 1 "started because $caller is $0" >/dev/null
    else
      create.result 0 "sourced because called by $callerFunction in $caller" >/dev/null
    fi
  esac
  info.log "return result $(result)"
  return $(result)
}

# shellcheck disable=SC2120
this.restart()
{
    important.log "exited back into this $STARTED: exit code: $RETURN_VALUE"
    unset STARTED
    export STARTED

    if [ -z "$RETURN_VALUE" ]; then
      important.log "no return value set....propably on MacOS...mitigating"
      source ~/config/result.env
      export RETURN_VALUE=$RESULT_RETURN_VALUE
      important.log "found RETURN_VALUE=$RETURN_VALUE"
    fi

    if [ "$1" = "again" ] || [ "$RETURN_VALUE" -eq "$ERROR_CODE_RECONFIG" ] ; then 
      echo "
      restarting again $?
      "
      $OOSH_DIR/this restart
    fi
}


this.start() {
  local len=${#BASH_LINENO[@]}
  local caller=${BASH_SOURCE[$len - 2]##*/}
  local callerFunction=${FUNCNAME[1]}

  This=$(which $caller)
  if (this.isSourced); then
    info.log "is sourced"
    export RESTULT
    return 0
  fi

  if [ -z "$1" ]; then
    if (this.functionExists $caller.usage); then
      $caller.usage
    else
      console.log "$0: no parameter: Bye"
    fi
  fi


  while [ -n "$1" ]; do
    info.log "start 1: -$1-"
    case $1 in
    # call)
    #   shift
    #   "$@"
    #   ;;
    # discover)
    #   once.discover
    #   if [ "$ONCE_STATE" = "disvocer" ]; then
    #     ONCE_STATE=check.installation
    #     once.stage
    #   fi
    #   ;;
    start)
      stop.log "once start"
      once.server.start "$@"
      ;;
    '')
      debug.log "$0: EXIT"
      #exit 0
      return $RETURN_VALUE
      ;;
    help*)
      this.$1 $This "$@"
      ;;
    localInstall)
      echo "local install mode"
      ;;
    restart)
      if [ -z "$BASH_FILE" ]; then
        export BASH_FILE="$(which bash)"
      fi
      echo "restarting $BASH_FILE: $RETURN_VALUE = $ERROR_CODE_RECONFIG"


      if [ "$RETURN_VALUE" -eq "$ERROR_CODE_RECONFIG" ]; then
        export SHELL=$BASH_FILE
        $BASH_FILE #-x # force debug
        RETURN_VALUE=$?
        #      this.restart again
      else
        echo "finally exiting oosh"
      fi
      ;;
    *)
      debug.log "this.call to: $*"
      this.call "$@"
      RETURN_VALUE=$?
    #  ;;
    esac

    shift

    while [ ! "$RETURN" = "$1" ]; do
      shift
      debug.log "shift:  -Return:$RETURN-  -$1- -command=$COMMANDS-  =$*="
      if [ -z "$1" ]; then
        debug.log "force stop"
        RETURN=
        exit $RETURN_VALUE
      fi
    done
    debug.log "found RETURN=$1"
    RETURN=$2

  done
  debug.log "will stage"
  [ -n "$*" ] && this.call "$@"

  return $RETURN_VALUE
}


###################################
info.log "
still in ${BASH_SOURCE[0]##*/}
"
#set -x

this.init



case "${0##*/}" in
  "init"|"oosh"|"log"|"this")
    #source $OOSH_DIR/loop
    #LOG_LEVEL=6
    this.path.add "$OOSH_DIR/external"
    this.path.add "$OOSH_DIR/init"
    this.path.add "."
    this.path.add "$OOSH_DIR"
    this.path.add "$OOSH_DIR/ng"

    if [ "$USER" = "root" ]; then
      this.path.add "$OOSH_DIR/su"
    fi

    if [ -n "$OOSH_CONFIG_NEEDS_SAVE" ]; then
      unset OOSH_CONFIG_NEEDS_SAVE
      source $OOSH_DIR/config 
      config.save
    fi
  ;;
  "config"|"loop")
    info.log "  running ${0##*/}"
  ;;
  *)
    info.log "  running unknown ${0##*/}"
  ;;
esac






if (this.isSourced); then
  info.log "this was sourced"
  #set -x

else
  if [ -z "$STARTED" ]; then
    echo "this was started as $0" 


    case "${0##*/}" in
      "init"|"oosh"|"log"|"this")
        #source $OOSH_DIR/loop
        #LOG_LEVEL=6
        info.log "  running ${0##*/}"
      ;;
      "config"|"loop")
        info.log "  running ${0##*/}"
      ;;
      *)
        info.log "  running unknown ${0##*/}"
      ;;
    esac

    if ! [ -f $OOSH_DIR/init/once ]; then
      rm -r $OOSH_DIR/init
      ln -s $OOSH_DIR/oosh/init
    fi
    config ci
    if [ -z "$BASH_FILE" ]; then
      export BASH_FILE="$(which bash)"
      #   echo "BASH_FILE was not set"
      # else 
      #   echo "BASH_FILE is set to: $BASH_FILE"
    fi
    echo running bash with following settings
    echo -------------------------------------
    echo  OOSH_DIR=$OOSH_DIR
    echo      PATH=$PATH
    echo BASH_FILE=$BASH_FILE
    echo    CONFIG=$CONFIG
    echo ARGUMENTS=$*
    echo -------------------------------------


    if [ -n "$1" ]; then
      echo using arguments $*
      #LOG_LEVEL=6  # force debug
      #set -x
      this.start "$@"
      #exit 0
    fi

    export STARTED="true"
    export EXPECTED_RETURN_VALUE=117

    # TDOD move into this start?
    if [ "$1" = "localInstall" ]; then 

        oo state
        # HACK needed to get the package manager right for the first time
        user get home
        # crashed the state next otherwise 





        important.log "
        
        still during oosh installation

        continue with:

          state
          state next

        "
    fi
    export SHELL=$BASH_FILES
    $BASH_FILE #-x # force debug
    this.restart


  else
    if [ -z "$1" ]; then
      console.log "
      ${GREEN}oosh is already started $GRAY(shell level: $SHLVL)$NORMAL
      
      Welcome to 4.0 again      
      "
    else
      important.log "starting this with: $@"
      this.start "$@"
    fi
  fi
fi
