#!/usr/bin/env bash
#clear
#export PS4='\e[90m+${LINENO} in ${#BASH_SOURCE[@]}>${FUNCNAME[0]}:${BASH_SOURCE[@]##*/} \e[0m'
#set -x

#echo "starting: $0 <LOG_LEVEL=$1>"

### new.method

line.use() { # <file: > # uses file as input 
  local file="$1"
  if [ -n "$1" ]; then
    file="$1"
    shift
  fi

  cat "$file" | this.start "$*"
}

line.split() { # <?seperator: > # splits the line (for env variables use split.env  because of quoting)
  local seperator="$1"
  if [ -z "$seperator" ]; then
    cat - | tr ' ' '\n'
    return $?
  fi
  line.split.quoted "$seperator"
}

line.split.quoted() { # <?word:"="> <?by:""> # use when the original line is already quoted: "some line = split on equlas"
  local word="$1"
  if [ -n "$1" ]; then
    word="$1"
    shift
  else
    word='='
  fi

  local by="$1"
  if [ -n "$1" ]; then
    by="$1"
    shift
  else
    by="'"$'\\\n'"'"
  fi

  #cat - | tr "$seperator" "\n"
  cat - | line.quote | sed "s/$word/$by/g"
}

line.split.env() { # # 
  local word="$1"
  if [ -n "$1" ]; then
    word="$1"
    shift
  else
    word='='
  fi
  cat - | line.quote | line.split.quoted "$word"
}

line.format.env() {       # <?format:"FORMAT_ENV"> # 
    local format="$1"
  if [ -n "$1" ]; then
    format="$1"
    shift
  else
    format="${RED}%-40s${YELLOW}=${GREEN} %-20s\n"
  fi
  cat - | line.split.env | line.format "$format"
}

line.replace() { # <?word: > <?by:> # replace <word> with <by>.
  local word="$1"
  if [ -n "$1" ]; then
    word="$1"
    shift
  else
    cat -
    return 1
    #word=''
  fi

  local by="$1"
  if [ -n "$1" ]; then
    by="$1"
    shift
  else
    by=''
  fi

  #cat - | tr "$seperator" "\n"
  info.log "line replace -word:$word- -by:$by-"
  #cat - | sed "s/$word/$by/g"
  cat - | sed "s/$word/$by/"
}

line.replace.sedquoted() { # <?word: > <?by:> # replace <word> with <by>.
  local word="$1"
  if [ -n "$1" ]; then
    word="$( echo $1 | sed 's/\([/(){}]\)/\\\1/g' )"
    shift
  else
    word=''
  fi

  local by="$1"
  if [ -n "$1" ]; then
    by="$( echo $1 | sed 's/\([/(){}]\)/\\\1/g' )"
    shift
  else
    by=''
  fi

  #cat - | tr "$seperator" "\n"
  cat - | sed "s/$word/$by/g"
}

line.example.chevron() # # print a path with chevron charackters
{
  echo -e 'After: \x1b[45m vscode \x1b[7;33m⮀\x1b[0;43m ♡ \x1b[7;34m⮀\x1b[0;44m After: vscode ♡ \x1b[0;34m⮀\x1b[0m the character viewer UTF-8 sequence would be :\xE2\xAE\x80: .\xC2\xB2.'
}

line.filter() { # <?word: > <?by:> # replace <word> with <by>.
  local by="$1"
  if [ -n "$1" ]; then
    by="$1"
    shift
  else
    by=''
  fi

  #cat - | tr "$seperator" "\n"
  cat - | grep -v "$by"
}

line.filter.comment.shell() {
  cat - | line.filter "^[ \t]*#"
}


line.keys() { # <?seperator:" "> #
  local seperator="$1"
  if [ -z "$seperator" ]; then
    seperator=' '
  fi
  cat - | cut -d "$seperator" -f1
}

line.values() { # <?seperator:" "> #
  local seperator="$1"
  if [ -z "$seperator" ]; then
    seperator=' '
  fi
  cat - | cut -d "$seperator" -f2
}

line.select() {               # <?lineNumber> #
  cat - | sed -n "$1p"
}

line.remove() {               # <?lineNumber> #
  if this.isNumber "$1"; then
    cat - | sed -n "$1d"
  else
    cat - | grep -v "$1"
  fi
}


line.count()  # # outputs the number of lines found
# optional there can be a message specified as <prefix> <postfix>
# the output contains the counter inbetween <prefix> and <postfix>
{
  count=$( cat - | grep -c "$" )
  echo "${1}${count}${2}" 
}

line.join() {                 # <?charakter=".">  #
  local char="$1"
  if [ -n "$1" ]; then
    shift
  else
    char="."
  fi

  #cat - | xargs | sed -e "s/ /$char/g"
  cat - | line format "%s$char" | line.replace "$char$"

}

line.into() {
  result.into "$1"
}

line.key() { # <?charakter=" "> #
  line.split "$1" | line.select 1
}

line.value() { # <?charakter=" "> #
  line.split "$1" | line.select 2
}

line.trim() { # #
  #all
  cat - | xargs 
}

line.trim.leading() { # #
  #leading
  cat - | sed -e 's/^[[:space:]]*//'
}

line.trim.trailing() { # #
  #trailing
  cat - | sed -e 's/[[:space:]]*$//'
}

line.trim.quoted.leading() { # #
  #trailing
  cat - | sed -e "s/^'[[:space:]]*/'/"
}

line.trim.quoted.trailing() { # #
  #trailing
  cat - | sed -e "s/[[:space:]]*'$/'/"
}

line.trim.quoted() { # #
  #trailing
  cat - | line.trim.quoted.leading | line.trim.quoted.trailing
}

line.dots() { # #
  line.space2 '.'
}

line.space2() { # <?charakter=" "> #
  local replace="$1"
  if [ -z "$replace" ]; then
    replace='.'
  fi
  cat - | sed -e "s/[[:space:]]/$replace/g"
}

line.find() { # <from> <to:"[empty line]"> #
  local from="$1"
  if [ -n "$1" ]; then
    from="$1"
    shift
  else
    cat - 
    return 1
    #error.log "no parameter 'from' provided"
  fi

  local to="$1"
  if [ -n "$1" ]; then
    to="$1"
    shift
  else
    to="$"
  fi

  if this.isNumber "$to"; then
    cat - | grep -A "$to" "$from"
  else
    cat - | awk "/$from/,/$to/" 
  fi
}



line.table() { # <?seperator:";"> <?heading:" ">  # prints a table (standad for csv)
  local seperator="$1"
  if [ -n "$1" ]; then
    seperator="$1"
    shift
  else
    seperator=";"
  fi

  local heading="$1"
  if [ -n "$1" ]; then
    heading="$1"
    shift
    #https://github.com/jorinvo/prepend
    cat - | line.prepend "${GREEN}$heading${NORMAL}" | column -t -s "$seperator"
    return 0
  fi

  cat - | column -t -s "$seperator"

}

line.format()  # <?format> <argumentList> # formats the inputed argumentList
# <?format> can be either a printf command line
# or        a predefined name from $CONFIG_PATH/lineFormat.env
#
# if a format does not exist user: line format.force.update
# to recreate the file... be aware if you would loose cutom formats you added yourself
{
  #set -x
  local format="$1"
  if [ -n "$1" ]; then
    format="$1"
    shift
  else
    format="%s "
  fi

  case "${format}" in
    FORMAT_*)
      format="${!format}"
      ;;
  esac

  cat - | xargs  printf "$format"
  #cat - | tr '\n' '\0' | xargs -0 -n1 printf "$format"
}

line.find.function() { # <functionName> # finds a function that starts with <functionName>
  local functionName="$1"
  if [ -n "$1" ]; then
    functionName="$1"
    shift
  # else
  #   error.log "no functionName provided"
  fi

  cat - \
  | line.find "$functionName.*\(\)" "$" \
  | line.quote 
  
  #source $CONFIG_PATH/result.env
  #set | line.find "^PARAM_" | line.format.env
}

line.unify() # <sepearator> <defaultColumn1> <defaultColumnN> # unifies the a split result
# if you split "1,2,3,4" and "1,2,3" by "," you would get 4 or 3 rows.
# with unify you chan split both and make it as many lines as you add <defaultColumnN>
# emplty lines will be replaced by the <defaultColumnN> value which you provides
# e.g.
# echo "a,b,c" | line unify "," 1 2 3 4
#        results in             a b c 4 
{
  local seperator="$1"
  if [ -z "$seperator" ]; then
    seperator=' '
  else
    shift
  fi
  cat - \
    | line.quote \
    | private.line.unify.each "$seperator" "$@"
  
}

private.line.unify.each() { # # 
  local seperator="$1"
  if [ -z "$seperator" ]; then
    seperator=' '
  else
    shift
  fi
  read -r line

  while [ -n "$line" ]; do
    echo -e "$line" | line.split.quoted "$seperator" | private.unify.one "$@"
    read -r line 
  done
}

private.unify.one() {  # # 
  while [ -n "$1" ]; do
    read -r line
    if [ -n "$line" ]; then
      echo -e "$line"
    else
      printf "'%s'\n" "$1"
    fi
    shift
  done
}


line.each() { # <sepearator> <method> <value1> ... <valueN> #

  local seperator="$1"
  if [ -z "$seperator" ]; then
    seperator=' '
  else
    shift
  fi

  local method="$1"
  if [ -z "$seperator" ]; then
    method='line.some.method'
  else
    shift
  fi


  cat - \
    | line.quote \
    | line.split "$seperator" \
    | line.unquote \
    | private.line.each "$method" "$@"
  
}

private.line.each() { # <method> <valueList> # while iterating the <valueList> it calls <method> <value> on each line 

  local method="$1"
  if [ -z "$method" ]; then
    method='line.some.method'
  else
    shift
  fi

  read -r line

  while [ -n "$line" ]; do
    echo -e "$line" | "$method" "$1"
    read -r line 
    shift
  done
}

line.assign() # <value> # adds "=<value" to a line
{
  cat - \
  | line.replace "$" "=\"$1\""
}

line.prepend.all() { # <sepearator> <defaultColumn1> ... <defaultColumnN> #
  cat - \
  | line.format "$1%s\n"
}

line.get.parameter() { # <functionName> # parses the function parameters

  local functionName="$1"
  if [ -n "$1" ]; then
    functionName="$1"
    shift
  else
    error.log "no functionName provided"
  fi

  cat "$This" \
  | line.find.function $functionName \
  | line.format FORMAT_PARSE_HELP \
  | line.split '|' \
  | line.select 2 \
  | line.parse.paramList
  
  #source $CONFIG_PATH/result.env
  #set | line.find "^PARAM_" | line.format.env
}

line.parse.paramList() { # # parses a (yellow) list of parameters into $CONFIG_PATH/result.txt
  cat - \
  | line.split '<' \
  | line.replace '>' \
  | line.remove.emptyLines \
  | line.parse.param "$@" >$CONFIG_PATH/result.env

  # cat $CONFIG_PATH/result.env \
  # | line.replace "declare -l " \
  # | line.format.env

  # if [ "$LOG_LEVEL" -gt "3" ]; then
  #   source $CONFIG_PATH/result.env
  #   set | line.find "^PARAM_" | line.format.env
  # fi
}

line.parse.param() { # <value> # 
  cat - \
  | line.unify ':' "parameterName" "addDefaultValue" \
  | line.trim.quoted \
  | line.format FORMAT_DECLARE_LOCAL "$1" \
  | line.replace '?' 'OPTIONAL_'
}

line.parse.paramList.new() { # # parses a (yellow) list of parameters into $CONFIG_PATH/result.txt
  cat - \
  | line.split '<' \
  | line.replace '>' \
  | line.remove.emptyLines \
  | line.unquote \
  | private.line.each line.parse.param.new "$@" 
}


line.parse.param.new() { # <value> # 
  cat - \
  | line.unify ':' "parameterName" "addDefaultValue" "$1" \
  | line.trim.quoted \
  | xargs line param.declare

  # | line.format FORMAT_DECLARE_LOCAL \
  # | line.replace '?' 'OPTIONAL_'
}

line.param.declare()  # <name> <defaultValue> <assignedValue> # output a param declaration
# test
{
  local name="$1"
  if [ -n "$1" ]; then shift; fi
  local defaultValue="$1"
  if [ -n "$1" ]; then shift; fi
  local assignedValue="$1"
  if [ -n "$1" ]; then shift; fi

  if [ -z "$defaultValue" ]; then
    defaultValue="$name"
  fi

  if [ -z "$assignedValue" ] || [ "$assignedValue" = "-" ]; then
    assignedValue="$defaultValue"
  fi

echo "declare -- PARAM_$name=\"$assignedValue\"" | line.replace '?' 'OPTIONAL_'

}


line.remove.emptyLines() { # # works only non color free text. e.g. use strip.colort first
  cat - | line.filter "^[ \t\']*$"
}

line.filter.emptyLines() { # # works only non color free text. e.g. use strip.colort first
  cat - | line.remove.emptyLines
}

line.quote() { # #
  cat - | line.unquote | tr '\n' '\0' | xargs -0 -n1 printf "'%s'\n"

  #cat - | line.format "\'%s\' "
}

line.args() { # # 
  #printf "\'%s\'\n" "$( cat - )"

  cat - | line.format "\'%s\' "
}

line.prepend() { # # 
  echo -e "$1"
  cat -
}

line.add() { # # 
  cat -
  echo -e "'$1'"
}

line.strip.color() { # # 
  cat - \
  | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g'
}

line.unquote() { # # 
  cat - \
  | line.replace "^'" \
  | line.replace "'$"
}

line.declare() { # # pretty prints the current env 
  env | line.split = | line.join ' ' | xargs  printf 'declare -- %s=%s\n'
}


private.line.format.init() { # # 
  export FORMAT_ENV="${RED}%-20s ${YELLOW}= ${GREEN}%s${NORMAL}\n"
  export FORMAT_DECLARE_LOCAL="declare -- PARAM_%s='%s'\n"
  export FORMAT_HELP="      ${CYAN}%-20s ${YELLOW}| %-30s ${GREEN}| %s\n"
  export FORMAT_PARSE_HELP="%s|%s|%s\n"
  export FORMAT_PARSE_METHOD="declare -- METHOD='%s'|declare -- METHOD_PARAMETER='%s'|declare -- METHOD_DESCRIPTION='%s'\n"

  config save lineFormat "FORMAT_"
}

line.formats.edit() { # # edit $CONFIG_PATH/lineFormat.env
  vim $CONFIG_PATH/lineFormat.env
}

line.clean.config() {
  rm $CONFIG_PATH/color.env
  rm $CONFIG_PATH/color.names.env
  rm $CONFIG_PATH/bold.color.names.env
  rm $CONFIG_PATH/lineFormat.env
}

line.usage() { # # 
  local this=${0##*/}
  echo "You started" 
  echo "$0

  Usage:
  $this: command   description and Parameter

      usage     prints this dialog while it will print the status when there are no parameters          
      v         print version information
      init      initializes ...nothing yet
      ----      --------------------------"
  this.help
  echo "
  
  Examples
    $this v
    $this init
    echo cool.and.the.gang is mega | line split ' ' | line split . | line select 6
                                                                     line join '#'
    echo key=value | line key =
    echo \" key value\" | line trim | line key 
    echo \$PATH | line split : | sort  | uniq | line join :
    env | line find KEYC | line format.env | line value =
    ls -al | line table ' ' 'rights - sub owner group size month day time name link'

    cat $OOSH_DIR/init/once | line filter.comment.shell | line replace \"#\" \"${GREEN}#\" | line replace \"$\" \"\$NO_COLOR\"
  "
}

line.init() {
  REGEX_EMPTY_LINE="^$"
  REGEX_LINE_END="$"
  REGEX_BRACKETS="\(\)"
  REGEX_NEW_LINE=$'\\\n'

           ESC=$'\e['
          BOLD="${ESC}0;1m"
      NORMAL=${ESC}"0m"
    NO_COLOR=${ESC}"0m"
  
  {  
    echo         'ESC=$'\''\e['\'''
    echo        'BOLD="${ESC}0;1m"'
    echo    'NORMAL=${ESC}"0m"'
    echo  'NO_COLOR=${ESC}"0m"'
    echo  source '$CONFIG_PATH/color.env'
    echo  source '$CONFIG_PATH/color.names.env'
    echo  source '$CONFIG_PATH/bold.color.names.env'
  } >$CONFIG_PATH/setup.color.env

  
  if ! [ -f $CONFIG_PATH/color.env ];then
    {  
      echo     COLOR_RED="${ESC}31m"
      echo   COLOR_GREEN="${ESC}32m"
      echo    COLOR_BLUE="${ESC}0m"
      echo    COLOR_CYAN="${ESC}96m"
      echo    COLOR_GRAY="${ESC}90m"
      echo  COLOR_YELLOW="${ESC}33m"
      echo   COLOR_WHITE="${ESC}37m"
    } >$CONFIG_PATH/color.env
    cat $CONFIG_PATH/color.env | line.find "COLOR_" | line.replace COLOR_ >$CONFIG_PATH/color.names.env
    cat $CONFIG_PATH/color.env | line.find COLOR_ | line.replace COLOR_ BOLD_ | line.replace "{ESC}" "{ESC}1;" >$CONFIG_PATH/bold.color.names.env
    if [ -f $CONFIG_PATH/lineFormat.env ]; then
      rm $CONFIG_PATH/lineFormat.env
    fi    
  fi

  source $CONFIG_PATH/setup.color.env


  if ! [ -f $CONFIG_PATH/lineFormat.env ]; then
    private.line.format.init
  fi 
}

line.format.force.update() # # be carefull. if you added formats manually, they will be lost
{
  check file $CONFIG_PATH/lineFormat.env exists \
    call rm $CONFIG_PATH/lineFormat.env
 
  private.line.format.init
}


line.start() { # # 
  #echo "sourcing init"
  source this

  if [ -z "$CONFIG_PATH" ]; then
    source $OOSH_DIR/config
  fi

  line.init
  if [ -f $CONFIG_PATH/lineFormat.env ]; then
    source $CONFIG_PATH/lineFormat.env
  fi

  # if [ -z "$1" ]; then
  #   status.discover "$@"
  #   return 0
  # fi

  this.start "$@"
}

line.start "$@"

