#!/bin/bash
COMMAND="$(basename "$0")"
ZYPPERLOG="/var/log/zypper.log"

function ZL_usage() {
cat <<-EOF
	Usage: $COMMAND [-h] [-l FILE] [-r N] [-d YYYY[-MM[-DD]]] [PID]

	This tool helps you to access the zypper logfile '$ZYPPERLOG'.
	Run this command without any arguments to get a list of your
	zypper runs.
	Provide the PID of a zypper run as an argument to query the
	log for this run.

	Positional arguments:
	  PID            Get log for this PID

	Optional arguments:
	  -h, --help         show this help message and exit
	  -l FILE            Read only this file
	  -r N               Read N rotated logfiles
	  -d YYYY[-MM[-DD]]  Get runs for this date
EOF
}

function ZL_msg()
{ echo "$*" >&2; }

function ZL_printf()
{ printf "$@" >&2; }

function ZL_exit() {
  local RET="${1:-0}"
  shift
  (( $# != 0 )) && ZL_msg "$COMMAND: $*"
  exit $RET
}

function ZL_exit_parseargs() {
  ZL_exit 2 "$*"
}

# ######################################################################
# Parse command line arguments
options=$(getopt -n "$COMMAND" -o hl:r:d: --long help -- "$@")
(( $? != 0 )) && ZL_exit_parseargs

declare -i ROTATED=0
FILEARG=()
DATEARG=()
eval set -- "$options"
while (( $# != 0 )); do
  case "$1" in
    -h|--help)
      ZL_usage
      exit 0
      ;;
    -l)
      shift	# get argument
      # Access to the file check is delayed. Even if the stem does
      # not exist, rotated ones may be there. So we check them all
      # parse_stem.
      FILEARG+=("$1")
      ;;
    -r)
      shift	# get argument
      [[ $1 =~ ^[[:digit:]]+$ ]] || {
	ZL_exit_parseargs "-r: Invalid int value: '$1'"
      }
      ROTATED="$1"
      ;;
    -d)
      shift	# get argument
      [[ $1 =~ ^[[:digit:]]{4}(-[[:digit:]]{2}(-[[:digit:]]{2})?)?$ ]] || {
	ZL_exit_parseargs "-d: Time stamp '$1' does not match format 'YYYY[-MM[-DD]]'"
      }
      DATEARG+=(-e)
      DATEARG+=("^$1")	# add RX for grep later
      ;;
    --)
      shift
      break
      ;;
  esac
  shift
done

# Default to zypper.log
(( ${#FILEARG[@]} == 0 )) && {
  FILEARG+=("$ZYPPERLOG")
}

PIDARG=()
for PID in "$@"; do
  [[ $PID =~ ^[[:digit:]]+$ ]] || {
    ZL_exit_parseargs "PID: Invalid int value: '$PID'"
  }
  PIDARG+=(-e)
  PIDARG+=("^[^(]*($PID)")	# add RX for grep later
done

# ######################################################################
# main

function may_collect()
{
  local FILE="$1"
  local FORMAT="Collect from %s ... %s\n"

  [[ -f $FILE ]] || {
    ZL_printf "$FORMAT" "$FILE" "No such file"
    return 1
  }
  [[ -r $FILE ]] || {
    ZL_printf "$FORMAT" "$FILE" "Permission denied"
    return 1
  }
  ZL_printf "$FORMAT" "$FILE"
  return 0
}


function get_rotated()
{
  local STEM="$1"
  local -i ROTATED="$2"
  local -n _RETVAR="$3"

  (( $ROTATED > 0 )) && {
    local FILES=()
    for F in "$STEM"-????????.*; do
      FILES+=("$F")
    done
    (( $ROTATED < ${#FILES[@]} )) && {
      FILES=("${FILES[@]:$((-$ROTATED))}")
    }
    for F in "${FILES[@]}"; do
      may_collect "$F" && _RETVAR+=("$F")
    done
  }
  may_collect "$STEM" && _RETVAR+=("$STEM")
}

function cat_files()
{
  local -n _FILEARG=$1
  for F in "${_FILEARG[@]}"; do
    case "${F/*./}" in
      xz)
	xzcat "$F"
	;;
      bz2)
	bzcat "$F"
	;;
      gz)
	zcat "$F"
	;;
      *)
	cat "$F"
	;;
    esac
  done
}

function date_filter()
{
  local -n _DATEARG=$1
  grep "${_DATEARG[@]}"
}

function pid_filter()
{
  local -n _PIDARG=$1

  if (( ${#_PIDARG[@]} > 0 )); then
    grep "${_PIDARG[@]}"
  else
    # Zypper toc:
    #  ] main.cc(main):97 ===== Hi, me zypper 1.14.30
    #  ] main.cc(main):98 ===== 'zypper' 'ref' =====
    # YAST toc:
    #  ] bin/y2start:16 y2base called with ["menu", "ncurses"]
    #  legacy YCP:
    #  ] genericfrontend.cc(main):675 Launched YaST2 component 'y2base' 'installation' '("continue")' 'qt'
    grep -a '\] \(main\.cc(\|bin/y2start:\|genericfrontend\.cc(\)' |
    awk '
      function outh() {
	F="%-16s %6s %8s  %s\n"
	printf F,"TIME","PID","VER","CMD"
      }
      function outl( TD,TH,RP,V,C ) {
	if ( !F ) outh()
	sub ( ":..$", "", TH )
	gsub( ".*\\(|\\)", "", RP )
	printf F,TD" "TH,RP,V,C
      }
      ( $7 == "=====" && $8 == "Hi," ) { V = $11; next; }
      ( $7 == "=====" && substr( $8, 0, 1 ) == "'\''"  ) {
        C = $0
	gsub( ".*===== '\''|'\'' =====.*$", "", C )
	gsub( "'\'' '\''", " ", C )
        outl( $1,$2,$4,V,C )
	next
      }
      /y2base called with/ {
	C = $0
	gsub( ".*y2base called with \\[\"|\"\\]$", "", C )
	gsub( "\", \"", " ", C )
	outl( $1,$2,$4,"----","y2base "C )
	next
      }
      /Launched YaST2 component/ {
	C = $0
	gsub( ".Launched YaST2 component '\''|'\''$", "", C )
	gsub( "'\'' '\''", " ", C )
	outl( $1,$2,$4,"----",C )
	next
      }
      END { if ( !F ) { outh(); print " n/a"; } }
    '
  fi
}

function parse_file()
{
  local -n _FILEARG=$1

  if (( ${#DATEARG[@]} > 0 )); then
    cat_files ${!_FILEARG} | date_filter DATEARG | pid_filter PIDARG
  else
    cat_files ${!_FILEARG} | pid_filter PIDARG
  fi
}

function parse_stem()
{
  local STEM="$1"
  local FILEARG=()

  ZL_msg "================================================================================"
  get_rotated "$STEM" "$ROTATED" FILEARG
  (( ${#FILEARG[@]} == 0 )) && {
    ZL_msg "No files to collect!"
    return 1
  }
  ZL_msg
  parse_file FILEARG
}

for F in  "${FILEARG[@]}"; do
  parse_stem "$F"
done
