#!/bin/bash
#---------------------------------------------------------------------
##
## @Synopsis Set of functions used by sorcery for queue generation
##
## @Copyright Original version Copyright 2001 by Kyle Sallee
## @Copyright Additions/Corrections Copyright 2002-2008 by the Source Mage Team
##
#---------------------------------------------------------------------

#---------------------------------------------------------------------
##
## Checks all installed spells for newer versions, and creates an
## install queue.
##
#---------------------------------------------------------------------
function update_install_queue()  {
  local line spell curr_version curr_updated page_dir info curr_patchlevel
  local curr_sec_patch count size
  local tmp_queue=$TMP_DIR/install_queue
  local recheck_queue=$TMP_DIR/recheck_queue
  touch $tmp_queue

  message  "${CHECK_COLOR}Generating the list of spells to update... ${DEFAULT_COLOR} "

  update_install_queue_sub "$tmp_queue" "$recheck_queue" || return 1
  if [[ -s $recheck_queue ]]; then
    (
      count=0
      size=$(wc -l < $recheck_queue)
      while read spell; do
          let count++
          progress_bar $count $size 50
          explode "$(search_spell_status $SPELL_STATUS "$spell")" ":" "info"
          curr_updated=${info[1]}
          curr_version=${info[3]}
          codex_set_current_spell_by_name $spell &&
          does_spell_need_update_slow "$spell" "$curr_version" \
                                    "$curr_updated" "$tmp_queue"
      done < $recheck_queue
      clear_line
    )
    rm $recheck_queue
  fi
  lock_file $INSTALL_QUEUE
  rm  -f  $INSTALL_QUEUE
  sort -u $tmp_queue > $INSTALL_QUEUE
  unlock_file $INSTALL_QUEUE

}

#---------------------------------------------------------------------
##
## Finds all installed spells that need an update due to security
## reasons and creates an install queue.
##
#---------------------------------------------------------------------
function update_security_install_queue()  {
  local line spell page_dir info curr_sec_patch=0
  local tmp_queue=$TMP_DIR/install_queue
  local recheck_queue=$TMP_DIR/recheck_queue
  touch $tmp_queue

  message  "${CHECK_COLOR}Generating the list of spells to update for" \
           "security reasons... ${DEFAULT_COLOR} "

  update_install_queue_sub "$tmp_queue" "$recheck_queue" security || return 1
  if [[ -s $recheck_queue ]]; then
    (
      count=0
      size=$(wc -l < $recheck_queue)
      while read spell; do
          let count++
          progress_bar $count $size 50
          if codex_set_current_spell_by_name $spell &&
             tablet_find_spell_dir $spell page_dir; then
            tablet_get_security_patch $page_dir curr_sec_patch &&
            diff=$(( ${SECURITY_PATCH:-0} - $curr_sec_patch ))
            if (( $diff > 0 )); then
              echo $spell >> $tmp_queue
              clear_line
              if (( $diff > 1 )); then
                message "$SPELL_COLOR$spell$DEFAULT_COLOR: Security update ($diff times!)."
              else
                message "$SPELL_COLOR$spell$DEFAULT_COLOR: Security update."
              fi
            fi
          fi
      done < $recheck_queue
      clear_line
    )
    rm $recheck_queue
  fi

  lock_file $INSTALL_QUEUE
  rm  -f  $INSTALL_QUEUE
  sort -u $tmp_queue > $INSTALL_QUEUE
  unlock_file $INSTALL_QUEUE

}

#---------------------------------------------------------------------
## Find all spells that need to be updated due to security reasons.
## Checks the version caches and compares them to the installed one.
#---------------------------------------------------------------------
function find_security_updates() {
  local tmp_queue=$1
  local recheck_queue=$2

  awk -v i=$VERSION_STATUS -v verbose=$VERBOSE_QUEUING '
    # decide what to print - just the spell or also the reason
    function needs_update(                diff) {
      if (verbose == "on") {
        diff = factor[4] - $4
        if (diff > 0) {
          if (diff > 1) {
            print $1, "Security update (" diff " times!)."
          } else {
            print $1, "Security update."
          }
        }
      } else {
        if ($4 < factor[4]) {
          print $1
        }
      }
    }

    {
      if ( ! ($1 in map)) {
        map[$1]=$0;
      }
    }

    END {
      while (getline < i) {
        if ($1 in map && ! match(map[$1],"multiversioned")) {
          split(map[$1], factor);
          needs_update();
        } else {
          # multiversioned spell - do it the old way
          print $1 > "/dev/stderr";
        }
      }
    } ' > $tmp_queue 2> $recheck_queue
}

if [ "$LIBQUEUE_VERCMP" = newer ]; then
  awk_version_compare='
	function max(a, b) { return a > b ? a : b }
	function version_cmp(v1, v2,		i, a, b) {
		v1 = max(split(v1, a, /[-_.:+~]/), split(v2, b, /[-_.:+~]/))
		for (i = 1; i <= v1; i++)
			if (v2 = a[i] - b[i])
				break
		return v2
	}
'
else
  awk_version_compare='function version_cmp(v1, v2) { return -(v1 != v2) }'
fi
function version_compare_gt() {
  awk "$awk_version_compare"'
	BEGIN {
		exit !(version_cmp(ARGV[1], ARGV[2]) < 0)
	}
' "$@"
}

#---------------------------------------------------------------------
## Find all spells that need to be updated (for any reason).
## Checks the version caches and compares them to the installed one.
#---------------------------------------------------------------------
function find_updates() {
  local tmp_queue=$1
  local recheck_queue=$2

  awk -v i=$VERSION_STATUS -v verbose=$VERBOSE_QUEUING "$awk_version_compare"'
    # decide what to print - just the spell or also the reason
    function needs_update(               reasons, diff) {
      if (verbose == "on") {
        # check SECURITY_PATCH first since it is the most important
        diff = factor[4] - $4
        if (diff > 0) {
          if (diff > 1) {
            reasons = reasons " Security update (" diff " times!)."
          } else {
            reasons = reasons " Security update."
          }
        }
        if (version_cmp($2, factor[2]) < 0) {
          reasons = reasons " New version (" factor[2] ")."
        }
        if ($3 < factor[3] ) {
          reasons = reasons " Spell update."
        }
        if ($5 < factor[5]) {
          reasons = reasons " UPDATED changed."
        }
        if (reasons != "") {
          print $1, reasons
        }
      } else {
        if (version_cmp($2, factor[2]) < 0 || $3 < factor[3] || $4 < factor[4] || $5 < factor[5]) {
          print $1
        }
      }
    }

    {
      if ( ! ($1 in map)) {
        map[$1]=$0;
      }
    }

    END {
      while (getline < i) {
        if ($1 in map && ! match(map[$1],"multiversioned")) {
          split(map[$1], factor);
          needs_update();
        } else {
          # multiversioned spell - do it the old way
          print $1 > "/dev/stderr";
        }
      }
    }' > $tmp_queue 2> $recheck_queue
}

#---------------------------------------------------------------------
## Frontend routine to encapsulate whether or not a spell needs updating
## Used by lazy dependency resolution
#---------------------------------------------------------------------
function does_spell_need_update() {
  local spell=$1
  local tmp_queue=$TMP_DIR/install_queue
  local recheck_queue=$TMP_DIR/recheck_queue
  local rc

  VERBOSE_QUEUING=on
  update_install_queue_sub "$tmp_queue" "$recheck_queue" single $spell
  rc=$?
  rm $tmp_queue $recheck_queue
  return $rc
}

#---------------------------------------------------------------------
## The slow way of determining if a spell needs to be updated. Doesn't
## use the version cache and is also needed for checking multiversioned
## spells.
#---------------------------------------------------------------------
function does_spell_need_update_slow() {
  local spell=$1
  local curr_version=$2
  local curr_updated=$3
  local tmp_queue=$4
  local message="$SPELL_COLOR$spell$DEFAULT_COLOR:"

  local curr_patchlevel=0
  local curr_sec_patch=0
  if tablet_find_spell_dir $spell page_dir; then
    curr_updated=0
    tablet_get_updated $page_dir curr_updated
    tablet_get_patchlevel $page_dir curr_patchlevel
    tablet_get_security_patch $page_dir curr_sec_patch
  fi

  local diff=$(( ${SECURITY_PATCH:-0} - $curr_sec_patch ))
  if (( $diff > 0 )); then
    if (( $diff > 1 )); then
      message="$message Security update ($diff times!)."
    else
      message="$message Security update."
    fi
  fi
  if version_compare_gt $VERSION $curr_version; then
    message="$message New version ($VERSION)."
  fi
  if (( "${PATCHLEVEL:-0}" > "$curr_patchlevel" )); then
    message="$message Spell update."
  fi
  if (( "${UPDATED:-0}" > "${curr_updated:-0}" )); then
    message="$message UPDATED changed."
  fi
  if [[ $message == "$SPELL_COLOR$spell$DEFAULT_COLOR:" ]]; then
    # no updates found
    return 1
  fi
  if [[ $VERBOSE_QUEUING == on ]]; then
    clear_line
    message "$message"
  fi
  echo $spell >> $tmp_queue
  return 0
}

#---------------------------------------------------------------------
## Common code between update_install_queue,
## update_security_install_queue and does_spell_need_update
#---------------------------------------------------------------------
function update_install_queue_sub()  {
  local tmp_queue=$1
  local recheck_queue=$2
  local type=$3
  local spell=$4
  local rc

  if [[ -z $OLD_QUEUING_METHOD ]]; then
    tablet_check_version_cache $VERSION_STATUS || return 1
    local grimoire
    for grimoire in $(codex_get_all_grimoires); do
      if [[ -f $grimoire/$VERSION_INDEX_FILE ]]; then
        cat $grimoire/$VERSION_INDEX_FILE
      else
        # provide a dummy version index, treating all spells as multiversioned
        sed -n "s|^\(\S*\) .*$|\1 multiversioned|p" $grimoire/$SPELL_INDEX_FILE
      fi
    done |
    if [[ $type == security ]]; then
      find_security_updates "$tmp_queue" "$recheck_queue"
    else
      find_updates "$tmp_queue" "$recheck_queue"
    fi

    if [[ $type == single ]]; then
      if grep -q "^$spell " "$tmp_queue"; then
        sed -i "/^$spell /!d" "$tmp_queue"
      elif grep -q "^$spell$" "$recheck_queue"; then
        # the fast method failed, recheck the old slower way
        update_install_queue_slow $spell
        return $?
      else
        return 2
      fi
    else
      for spell in $(get_all_spells_with_status held); do
        sed -i "/^$spell\( \|$\)/d" $tmp_queue $recheck_queue
      done
    fi
    if [[ $VERBOSE_QUEUING == on ]]; then
      local message
      while read spell message; do
        message "$SPELL_COLOR$spell$DEFAULT_COLOR: $message"
      done < $tmp_queue
      sed -i 's,^\(\S*\)\s.*$,\1,' $tmp_queue
    fi
    return 0
  else # use the old slow method
    if [[ $type == single ]]; then
      update_install_queue_slow $spell
    else
      get_all_spells_with_status installed > $recheck_queue
    fi
  fi
}

#---------------------------------------------------------------------
## Slow higher-level check if a spell needs to be updated
#---------------------------------------------------------------------
function update_install_queue_slow()  {
  local spell=$1
  # this is in a subshell because the caller may not want to actually load
  # the spell yet
  (
    line=$(search_spell_status $SPELL_STATUS $spell)
    codex_set_current_spell_by_name  $spell
    explode "$line" ":" "info"
    does_spell_need_update_slow "$spell" "${info[3]}" "${info[1]}" /dev/null
  )
}

#---------------------------------------------------------------------
## @Stdout install queue
##
## List files in install queue, give user chance to modify it.
##
#---------------------------------------------------------------------
function list_install_queue() {
  if [[ -f $INSTALL_QUEUE ]]; then
    lock_file $INSTALL_QUEUE

    echo
    message -n "The following spells will be updated:"
    message "${SPELL_COLOR}"
    column  $INSTALL_QUEUE
    message "${DEFAULT_COLOR}"

    query "Do you wish to edit$FILE_COLOR $INSTALL_QUEUE $DEFAULT_COLOR?" n &&
    edit_file $INSTALL_QUEUE

    if [[ -s $INSTALL_QUEUE ]]; then
      SPELLS=$(< $INSTALL_QUEUE)
    fi

    unlock_file $INSTALL_QUEUE
  else
    message "${MESSAGE_COLOR}No spells listed in the queue.$DEFAULT_COLOR"
  fi
}

#---------------------------------------------------------------------
## @Stdout history
##
## Display the history of the install queue for review
##
#---------------------------------------------------------------------
function install_queue_history() {
  local spell
  # we can't use read, since the loop needs a normal stdin
  for spell in $(< $INSTALL_QUEUE); do
    lock_file "$SPELL_STATUS"
    local date=$(grep "^${spell}:" $SPELL_STATUS | cut -d':' -f2)
    unlock_file "$SPELL_STATUS"
    local datedash=$(echo "$date" | sed 's,^\(....\)\(..\)\(..\)$,\1-\2-\3,')

    get_new_changes "Viewing history since last update ($datedash) for spell --  ${spell}  -- :" "$date" "$(codex_find_spell_by_name $spell)/HISTORY"

    if query "Would you like to ${RED}remove $SPELL_COLOR$spell$QUERY_COLOR from the install queue?" n; then
      pop_install_queue "$spell"
      message "${QUERY_COLOR}The spell $SPELL_COLOR$spell$QUERY_COLOR was removed from $FILE_COLOR$INSTALL_QUEUE$DEFAULT_COLOR."
    fi
  done
}

#---------------------------------------------------------------------
## @param queue filename
## @param items to remove ...
##
## The first argument is the name of the file containing the queue.
## If a second argument is given, any items in the queue that match
## the second argument are removed from the queue.  Otherwise, the top
## line of the queue is removed, and returned.
##
#---------------------------------------------------------------------
function pop_queue() {
  local item found
  local exit_code=0

  [[ -f $1 ]] || return 1

  esc_str "$2" item
  lock_start_transaction "$1" tQUEUE_FILE
  if [[ -n $item ]]; then
    grep -v "^$item\$" $1 > $tQUEUE_FILE
  else
    found=$(sed -n 1p $1)

    if [[ -z $found ]]; then
      exit_code=1
    else
      esc_str "$found" found
      grep -v "^$found$" $1 > $tQUEUE_FILE
      echo $found
    fi
  fi
  lock_commit_transaction "$1"

  return $exit_code
}

#---------------------------------------------------------------------
## @param queue filename
## @param item to add
## The first argument is the name of the file containing the queue.
## The second argument is an item to add to the end of the queue.  If
## the item exists anywhere in the queue, the item is removed from the
## queue before being added at the end.
##
#---------------------------------------------------------------------
function push_queue() {
  local t_file

  pop_queue "$1" "$2"

  lock_start_transaction "$1" t_file
  echo "$2" >> "$t_file"
  lock_commit_transaction "$1"
}

#---------------------------------------------------------------------
## @param filename
##
## The first argument is the name of the file that installed correctly
## and will be removed from the install file (this makes a failure of
## the casting of the queue not have to recast completed spells).
##
#---------------------------------------------------------------------
function pop_install_queue() {

  pop_queue  $INSTALL_QUEUE  "$1"

}

#---------------------------------------------------------------------
## @param spell
##
## Adds the given spell to the install queue and removes it from the
## remove queue.
##
#---------------------------------------------------------------------
function push_install_queue() {

  pop_queue   $REMOVE_QUEUE    "$1"
  pop_queue   $INSTALL_QUEUE   "$1"
  !  spell_installed           "$1"  &&
  push_queue  $INSTALL_QUEUE   "$1"

}

#---------------------------------------------------------------------
## @param spell
##
## Adds the given spell to the remove queue and removes it from the
## install queue.
##
#---------------------------------------------------------------------
function push_remove_queue() {

  pop_queue   $INSTALL_QUEUE  "$1"
  pop_queue   $REMOVE_QUEUE   "$1"
  spell_installed             "$1"  &&
  push_queue  $REMOVE_QUEUE   "$1"

}


#---------------------------------------------------------------------
##
## This software is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This software is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this software; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##
#---------------------------------------------------------------------
