
############################################################### smallutils

smallyes() {
  YES="${1-y}"
  while true ; do echo "$YES" ; done
}

############################################################### interaction

error () {
  if [ "$USE_BOOTFLOPPIES_INTERACTION" ]; then
    echo "E: $2" >&3
  else
    echo "E: $2" >&2
  fi
  exit $1
}

warning () {
  if [ "$USE_BOOTFLOPPIES_INTERACTION" ]; then
    echo "W: $1" >&3
  else
    echo "W: $1" >&2
  fi
}

info () {
  if [ "$USE_BOOTFLOPPIES_INTERACTION" ]; then
    echo "I: $1" >&3
  else
    echo "I: $1"
  fi
}

PROGRESS_NOW=0
PROGRESS_END=0
PROGRESS_NEXT=""
PROGRESS_WHAT=""

progress_next () {
  PROGRESS_NEXT="$1"
}

wgetprogress () {
  [ ! "$verbose" ] && QSWITCH="-q"
  local ret=0
  if [ "$USE_BOOTFLOPPIES_INTERACTION" -a "$PROGRESS_NEXT" ]; then
    wget "$@" 2>&1 >/dev/null | $PKGDETAILS "WGET%" $PROGRESS_NOW $PROGRESS_NEXT $PROGRESS_END "$PROGRESS_WHAT" >&3
    ret=$?
  else
    wget $QSWITCH "$@"
    ret=$?
  fi
  return $ret
}

progress () {
  if [ "$USE_BOOTFLOPPIES_INTERACTION" ]; then
    PROGRESS_NOW="$1"
    PROGRESS_END="$2"
    PROGRESS_WHAT="$3"
    PROGRESS_NEXT=""
    echo "P: $1 $2 $3" >&3
  fi
}

############################################################# set variables

download_style () {
  case "$1" in
    "apt")
      if [ "$2" = "var-state" ]; then
	APTSTATE=var/state/apt
      else
        APTSTATE=var/lib/apt
      fi
      DLDEST=apt_dest
      DEBFOR=apt_debfor
      export APTSTATE DLDEST DEBFOR
      ;;
    *)
      error 1 "unknown download style"
      ;;
  esac
}

################################################# work out names for things

mirror_style () {
  case "$1" in
    "release")
      DOWNLOAD_DEBS=download_release
      ;;
    "main")
      DOWNLOAD_DEBS=download_main
      ;;
    *)
      error 1 "unknown mirror style"
      ;;
  esac
  export DOWNLOAD_DEBS
}

check_md5 () {
  # args: dest md5 size
  local expmd5="$2"
  local expsize="$3"
  relmd5=`md5sum < "$1" | sed -e 's/ .*$//'`
  relsize=`wc -c < "$1"`
  if [ "$expsize" -ne "$relsize" -o "$expmd5" != "$relmd5" ]; then
    return 1
  fi
  return 0
}

get () {
  # args: from dest [md5sum size] [alt {md5sum size type}]
  if [ -e "$2" ]; then
    info "Validating $2"
    if [ "$3" = "" ] || check_md5 $2 $3 $4; then
      return 0
    else
      rm -f "$2"
    fi
  fi
  if [ "$#" -gt 5 ]; then
    local st=3
    if [ "$5" = "-" ]; then st=6; fi
    local order="$(a=$st; while [ "$a" -le $# ]; do eval echo \"\$$(($a+1))\" $a;
                  a=$(($a + 3)); done | sort -n | sed 's/.* //')"
  else
    local order=3
  fi
  for a in $order; do
    local md5="$(eval echo \$$a)"
    local siz="$(eval echo \$$(( $a+1 )))"
    local typ="$(eval echo \$$(( $a+2 )))"
    local from
    local dest

    case "$typ" in
      "bz2") from="$1.bz2"; dest="$2.bz2" ;;
      "gz") from="$1.gz"; dest="$2.gz" ;;
      *) from="$1"; dest="$2" ;;
    esac

    info "Retrieving $from"
    if ! just_get "$from" "$dest"; then return 1; fi
    info "Validating $dest"
    if [ "$md5" = "" ] || check_md5 $dest $md5 $siz; then
      case "$typ" in
        "gz") gunzip "$dest" ;;
        "bz2") bunzip2 "$dest" ;;
      esac
      return 0
    else
      warning "$from was corrupt"
    fi
  done
  return 1
}

just_get () {
  # args: from dest
  local from="$1"
  local dest="$2"
  mkdir -p "${dest%/*}"
  if [ "${from#null:}" != "$from" ]; then
    error 1 "${from#null:} was not pre-downloaded"
  elif [ "${from#http://}" != "$from" -o "${from#ftp://}" != "$from" ]; then
    # http/ftp mirror
    if wgetprogress -O "$dest" "$from"; then
      return 0
    elif [ -s "$dest" ]; then
      local iters=0
      while [ "$iters" -lt 3 ]; do
        warning "Retrying failed download of $from"
        if wgetprogress -c -O "$dest" "$from"; then break; fi
        iters="$(($iters + 1))"
      done
    else
      rm -f "$dest"
      return 1
    fi
  elif [ "${from#file:}" != "$from" ]; then
    local base="${from#file:}"
    if [ "${base#//}" != "$base" ]; then
      base="/${from#file://*/}"
    fi
    if [ -e "$base" ]; then
      cp "$base" $dest
      return 0
    else
      return 1
    fi
  else
    error 1 "unknown location $from"
  fi
}

download () {
    "$DOWNLOAD_DEBS" $(echo "$@" | tr ' ' '\n' | sort)
}

debfor () {
    "$DEBFOR" "$@"
}

apt_debfor () {
  for p in "$@"; do (
    cd "$TARGET/var/cache/apt/archives"
    local chk=0
    for x in ${p}_*_*.deb; do
      if [ -e "$x" ]; then
        echo "/var/cache/apt/archives/$x"
        chk=1
      fi
    done
    if [ "$chk" = 0 ]; then return 1; fi
  ); done
}

apt_dest () {
  # args:
  #   deb package version arch mirror path
  #   pkg suite component arch mirror path
  #   rel suite mirror path
  case "$1" in
    "deb")
      echo "var/cache/apt/archives/${2}_${3}_${4}.deb" | sed 's/:/%3a/'
      ;;
    "pkg")
      local m="$5"
      m="debootstrap.invalid"
      #if [ "${m#http://}" != "$m" ]; then
      #  m=${m#http://}
      #elif [ "${m#file://}" != "$m" ]; then
      #  m="file_localhost_${m#file://*/}"
      #elif [ "${m#file:/}" != "$m" ]; then
      #  m="file_localhost_${m#file:/}"
      #fi

      printf "$APTSTATE/lists/"
      echo "${m}_$6" | sed 's/\//_/g'
      ;;
    "rel")
      local m="$3"
      m="debootstrap.invalid"
      #if [ "${m#http://}" != "$m" ]; then
      #  m=${m#http://}
      #elif [ "${m#file://}" != "$m" ]; then
      #  m="file_localhost_${m#file://*/}"
      #elif [ "${m#file:/}" != "$m" ]; then
      #  m="file_localhost_${m#file:/}"
      #fi
      printf "$APTSTATE/lists/"
      echo "${m}_$4" | sed 's/\//_/g'
      ;;
  esac
}

################################################################## download

get_release_md5 () {
  local reldest="$1"
  local path="$2"
  sed -n '/^[Mm][Dd]5[Ss][Uu][Mm]/,/^[^ ]/p' < $reldest | while read a b c; do
    if [ "$c" = "$path" ]; then echo "$a $b"; fi
  done | head -n 1
}

download_release_indices () {
  local m1=${MIRRORS%% *}
  local reldest="$TARGET/$($DLDEST rel $SUITE $m1 dists/$SUITE/Release)"
  progress 0 100 "Downloading Release file"
  progress_next 100
  get "$m1/dists/$SUITE/Release" $reldest || 
    error 1 "Failed getting release file $m1/dists/$SUITE/Release"

  TMPCOMPONENTS="$(sed -n 's/Components: *//p' $reldest)"
  for c in $TMPCOMPONENTS ; do
    case "$c" in
      main|*/main)
        COMPONENTS="$COMPONENTS $c"
        ;;
    esac	
  done
  
  if [ "$COMPONENTS" = "" ]; then
    mv $reldest "$reldest.malformed"
    error 1 "Invalid Release file, no main components"
  fi
  progress 100 100 "Downloading Release file"

  local totalpkgs=0
  for c in $COMPONENTS; do
      local subpath="$c/binary-$ARCH/Packages"
      local normmd="`get_release_md5 $reldest ${subpath}`"
      if [ "$normmd" = "" ]; then
        mv $reldest "$reldest.malformed"
        error 1 "Invalid Release file, no entry for $subpath"
      fi
      totalpkgs="$(( $totalpkgs + ${normmd#* } ))"
  done

  local donepkgs=0
  progress 0 $totalpkgs "Downloading Packages files"
  for c in $COMPONENTS; do
    local subpath="$c/binary-$ARCH/Packages"
    local path="dists/$SUITE/$subpath"
    local bz2md="`get_release_md5 $reldest ${subpath}.bz2`"
    local gzmd="`get_release_md5 $reldest ${subpath}.gz`"
    local normmd="`get_release_md5 $reldest ${subpath}`"
    local ext="$normmd ."
    if [ -x /usr/bin/bunzip2 -a "$bz2md" != "" ]; then
      ext="$ext $bz2md bz2"
    fi
    if [ -x /bin/gunzip -a "$gzmd" != "" ]; then
      ext="$ext $gzmd gz"
    fi
    progress_next "$(($donepkgs + ${normmd#* }))"
    for m in $MIRRORS; do
      local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m $path)"
      if get "$m/$path" "$pkgdest" $ext; then break; fi
    done
    donepkgs="$(($donepkgs + ${normmd#* }))"
    progress $donepkgs $totalpkgs "Downloading Packages files"
  done
}

download_release () {
  download_release_indices

  local m1=${MIRRORS%% *}

  local totaldebs=0
  for p in "$@"; do
    for c in $COMPONENTS; do
      local path="dists/$SUITE/$c/binary-$ARCH/Packages"
      local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m1 $path)"
      if [ ! -e "$pkgdest" ]; then continue; fi
      details="$($PKGDETAILS $p $m1 $pkgdest)"
      if [ -z "$details" ]; then continue; fi
      size="${details##* }"; details="${details% *}"
      totaldebs="$(($totaldebs + $size))"
    done
  done 

  local dloaddebs=0

  progress $dloaddebs $totaldebs "Downloading packages"
  for p in "$@"; do
    for c in $COMPONENTS; do
      local details=""
      for m in $MIRRORS; do
        local path="dists/$SUITE/$c/binary-$ARCH/Packages"
        local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m $path)"
        if [ ! -e "$pkgdest" ]; then continue; fi
        details="$($PKGDETAILS $p $m $pkgdest)"
        if [ -z "$details" ]; then continue; fi
        size="${details##* }"; details="${details% *}"
        md5="${details##* }"; details="${details% *}"
	progress_next "$(($dloaddebs + $size))"
        local debdest="$TARGET/$($DLDEST deb $details)"
        if get "$m/${details##* }" $debdest $md5 $size; then
          dloaddebs="$(($dloaddebs + $size))"
          details="done"
          break
        fi
      done
      if [ "$details" != "" ]; then
        break
      fi
    done
    progress $dloaddebs $totaldebs "Downloading packages"
    if [ "$details" != "done" ]; then
      error 1 "Couldn't download $p"
    fi
  done
}

download_main () {
  local m1=${MIRRORS%% *}
  progress 0 100 "Downloading Packages file"
  progress_next 100
  COMPONENTS=main
  export COMPONENTS
  for m in $MIRRORS; do
    for c in $COMPONENTS; do
      local path="dists/$SUITE/$c/binary-$ARCH/Packages"
      local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m $path)"
      if [ -x /bin/gunzip ] && get "$m/${path}.gz" "${pkgdest}.gz"; then
        rm -f ${pkgdest}
        gunzip ${pkgdest}.gz
      elif get "$m/$path" "$pkgdest"; then
        true
      fi
    done
  done
  progress 100 100 "Downloading Packages file"

  for p in "$@"; do
    for c in $COMPONENTS; do
      local details=""
      for m in $MIRRORS; do
        local path="dists/$SUITE/$c/binary-$ARCH/Packages"
        local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m $path)"
        if [ ! -e "$pkgdest" ]; then continue; fi
        details="$($PKGDETAILS $p $m $pkgdest)"
        if [ -z "$details" ]; then continue; fi
        size="${details##* }"; details="${details% *}"
        md5="${details##* }"; details="${details% *}"
        local debdest="$TARGET/$($DLDEST deb $details)"
        if get "$m/${details##* }" $debdest $md5 $size; then
          details="done"
          break
        fi
      done
      if [ "$details" != "" ]; then
        break
      fi
    done
    if [ "$details" != "done" ]; then
      error 1 "Couldn't download $p"
    fi
  done
}

################################################################ extraction

extract () { (
  cd "$TARGET"
  local p=0
  for pkg in $(debfor "$@"); do
    p="$(($p + 1))"
    progress "$p" "$#" "Extracting packages"
    info "Extracting $pkg..."
    ar -p ./$pkg data.tar.gz | zcat | tar -xf -
  done
  sync
); }

in_target_nofail () {
  if ! eval chroot "$TARGET" "$@" 2>/dev/null; then
    true
  fi
  return 0
}

in_target_failmsg () {
  local msg="$1"
  shift
  if ! eval chroot "$TARGET" "$@"; then
    warning "$msg"
    return 1
  fi
  return 0
}

in_target () {
  in_target_failmsg "Failure trying to run: chroot $TARGET $*" "$@"
}

###################################################### standard setup stuff

conditional_cp () {
  if [ ! -e "$2/$1" -a -e "$1" ]; then cp -a "$1" "$2/$1"; fi
}

setup_etc () {
  mkdir -p "$TARGET/etc"
  
  conditional_cp /etc/resolv.conf "$TARGET"
  conditional_cp /etc/hostname "$TARGET"
  
  if [ "$DLDEST" = apt_dest -a ! -e "$TARGET/etc/apt/sources.list" ]; then
    mkdir -p "$TARGET/etc/apt"
    for m in $MIRRORS; do
      local cs=""
      for c in $COMPONENTS; do
        local path="dists/$SUITE/$c/binary-$ARCH/Packages"
        local pkgdest="$TARGET/$($DLDEST pkg $SUITE $c $ARCH $m $path)"
        if [ -e "$pkgdest" ]; then cs="$cs $c"; fi
      done
      #if [ "$cs" != "" ]; then echo "deb $m $SUITE$cs"; fi
      m="http://debootstrap.invalid/"
      if [ "$cs" != "" ]; then echo "deb $m $SUITE$cs"; fi
    done > "$TARGET/etc/apt/sources.list"
  fi
}

setup_proc () {
  on_exit "umount $TARGET/proc"
  umount $TARGET/proc 2>/dev/null || true
  in_target mount -t proc proc /proc
}

setup_devices () {
  if [ -e $DEVICES_TARGZ ]; then
    (cd "$TARGET"; zcat $DEVICES_TARGZ | tar -xf -)
  else
    if [ -e /dev/.devfsd ]; then
      in_target mount -t devfs devfs /dev
    else
      error 1 "no $DEVICES_TARGZ. can't create devices"
    fi
  fi
}

setup_dselect_method () {
  case "$1" in
    "apt")
      mkdir -p "$TARGET/var/lib/dpkg"
      echo "apt apt" > "$TARGET/var/lib/dpkg/cmethopt"
      chmod 644 "$TARGET/var/lib/dpkg/cmethopt"
      ;;
    *)
      error 1 "unknown dselect method"
      ;;
  esac
}

################################################################### helpers

repeat () {
  local n="$1"
  shift
  while [ "$n" -gt 0 ]; do
    if "$@"; then
      break
    else
      n="$(( $n - 1 ))"
      sleep 1
    fi
  done
  if [ "$n" -eq 0 ]; then return 1; fi
  return 0
}

N_EXIT_THINGS=0
exit_function () {
  local n=0
  while [ "$n" -lt "$N_EXIT_THINGS" ]; do
    eval $(eval echo \${EXIT_THING_$n})
    n="$(( $n + 1 ))"
  done
  N_EXIT_THINGS=0
}

trap "exit_function" 0
trap "exit 129" 1
trap "error 130 \"Interrupt caught ... exiting\"" 2
trap "exit 131" 3
trap "exit 143" 15

on_exit () {
  eval `echo EXIT_THING_${N_EXIT_THINGS}=\"$1\"`
  N_EXIT_THINGS="$(( $N_EXIT_THINGS + 1 ))"
}
