#! /bin/bash
# vim: set filetype=bash:

# to-bzip3: convert .Z, .lzo, .zip, .rar, .gz, .bz2, .zst, and .lzma files to
# bz3 format.

# Copyright (C) 2023-2026 by Brian Lindholm.  This file is part of the
# littleutils utility set.
#
# The to-bzip3 utility 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 3, or (at your option) any later version.
#
# The to-bzip3 utility 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
# the littleutils.  If not, see <https://www.gnu.org/licenses/>.

# get command-line options
DELORIG='n'
LZMAUTIL='lzma_alone'
MAXCOMP='n'
OVERWRITE='y'
PREFER=''
RETAINTIME='n'
declare -i THREADS=1
while getopts 46dhkmnp:t opts ; do
  case $opts in
    4) PREFER='--prefer-family=IPv4' ;;
    6) PREFER='--prefer-family=IPv6' ;;
    d) DELORIG='y' ;;
    h) echo 'to-bzip3 1.4.0'
       echo 'usage: to-bzip3 [-(IPv)4] [-(IPv)6] [-d(el_orig)] [-k(eep_smallest)]'
       echo '                [-m(ax_compression)] [-n(o_overwrite)] [-p threads]'
       echo '                [-t(imestamp retain)] [-h(elp)] filename ...'
       exit 0 ;;
    k) DELORIG='small' ;;
    m) MAXCOMP='y' ;;
    n) OVERWRITE='n' ;;
    p) THREADS=$OPTARG ;;
    t) RETAINTIME='y' ;;
    *) echo 'to-bzip3 1.4.0'
       echo 'usage: to-bzip3 [-(IPv)4] [-(IPv)6] [-d(el_orig)] [-k(eep_smallest)]'
       echo '                [-m(ax_compression)] [-n(o_overwrite)] [-p threads]'
       echo '                [-t(imestamp retain)] [-h(elp)] filename ...'
       exit 1 ;;
  esac
done
shift $((${OPTIND}-1))
if [ "$MAXCOMP" = 'y' ]; then
  BZIP3_OPTS='-b 511'
else
  BZIP3_OPTS='-b 16'
fi
if [ $THREADS -eq 1 ]; then
  ENGINE='bzip3'
else
  if [ $THREADS -eq 0 ]; then
    if [ -r /proc/cpuinfo ]; then
      NCORE=$(grep -c '^processor' /proc/cpuinfo)
    else
      NCORE=$(nproc)
    fi
    ENGINE="bzip3 -j $NCORE"
  else
    ENGINE="bzip3 -j $THREADS"
  fi
fi

# set up traps
trap 'rm -f "$TMPFILE" ; exit 1' 1 2 3 13 15

# run through files
declare -i S0=0 S1=0
while [ $# -gt 0 ]; do

  # if file is actually a URL, attempt to download, otherwise verify that it already exists
  NAME="$1"
  if [ "${NAME:0:8}" = 'https://' -o "${NAME:0:7}" = 'http://' -o "${NAME:0:6}" = 'ftp://' ]; then
    wget $PREFER -q "$NAME"
    NAME="${NAME##*/}"
    if [ ! -f "$NAME" -o ! -r "$NAME" ]; then
      echo "$NAME failed to download!"
      shift; continue
    fi
  elif [ ! -f "$NAME" -o ! -r "$NAME" ]; then
    echo "$NAME is not a readable file!"
    shift; continue
  fi
  echo -n "${NAME}... "

  # determine filepath and check for writeability
  FILENAME="${NAME##*/}"
  FILEPATH="${NAME%${FILENAME}}"
  FILEPATH="${FILEPATH%/}"
  if [ "X$FILEPATH" = 'X' ]; then
    FILEPATH='.'
  fi
  if [ ! -w "$FILEPATH" ]; then
    echo 'failed because target directory is non-writeable'
    shift; continue
  fi

  # determine new name and check for availability
  if  [ "${NAME#${NAME%.bz2}}" = '.bz2' ]; then
    NEWNAME="${NAME%.bz2}.bz3"
  elif [ "${NAME#${NAME%.tbz}}" = '.tbz' ]; then
    NEWNAME="${NAME%.tbz}.tbz"
  elif  [ "${NAME#${NAME%.gz}}" = '.gz' ]; then
    NEWNAME="${NAME%.gz}.bz3"
  elif [ "${NAME#${NAME%.tgz}}" = '.tgz' ]; then
    NEWNAME="${NAME%.tgz}.tbz"
  elif [ "${NAME#${NAME%.bz3ma}}" = '.bz3ma' ]; then
    NEWNAME="${NAME%.bz3ma}.bz3"
  elif  [ "${NAME#${NAME%.bz3o}}" = '.bz3o' ]; then
    NEWNAME="${NAME%.bz3o}.bz3"
  elif  [ "${NAME#${NAME%.rar}}" = '.rar' ]; then
    NEWNAME="${NAME%.rar}.tar.bz3"
  elif  [ "${NAME#${NAME%.RAR}}" = '.RAR' ]; then
    NEWNAME="${NAME%.RAR}.tar.bz3"
  elif  [ "${NAME#${NAME%.Z}}" = '.Z' ]; then
    NEWNAME="${NAME%.Z}.bz3"
  elif  [ "${NAME#${NAME%.zip}}" = '.zip' ]; then
    NEWNAME="${NAME%.zip}.tar.bz3"
  elif  [ "${NAME#${NAME%.ZIP}}" = '.ZIP' ]; then
    NEWNAME="${NAME%.ZIP}.tar.bz3"
  elif  [ "${NAME#${NAME%.zst}}" = '.zst' ]; then
    NEWNAME="${NAME%.zst}.bz3"
  else
    NEWNAME="${NAME}.bz3"
  fi
  if [ "$OVERWRITE" = 'n' -a -e "$NEWNAME" ]; then
    echo 'skipped because target already exists'
    shift; continue
  fi

  # recompress based on filetype
  FILETYPE=$(file -b "$NAME" | cut -f1 -d ' ')
  FILETYPE="${FILETYPE,,}"
  # file 5.02 and earlier cannot detect lzma, listing "data" instead
  if [ "$FILETYPE" = 'data' -a "${NAME#${NAME%.lzma}}" = '.lzma' ]; then
    FILETYPE='lzma'
  fi
  TMPFILE=$(tempname -d "$FILEPATH" .to-bzip3_$$) || exit 99
  if [ "X$FILETYPE" = 'Xbzip3' ]; then
    echo 'already converted to lz'
    rm -f "$TMPFILE"
    shift; continue
  elif [ "X$FILETYPE" = 'Xbzip2' ]; then
    bzip2 -c -d -q "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on bzip2-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  elif [ "X$FILETYPE" = 'Xgzip' ]; then
    gzip -c -d -q "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on gzip-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  elif [ "X$FILETYPE" = 'Xlzma' ]; then
    if [ "X$LZMAUTIL" = 'Xlzma_alone' ]; then
      lzma_alone d "$NAME" -so | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    else
      lzma -c -d -q "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    fi
    if [ $? -ne 0 ]; then
      echo 'failed on lzma-to-bzip3 conversion'
      rm -f "$TMPFILE"
    else
      CONVERTED='y'
    fi
  elif [ "X$FILETYPE" = 'Xlzop' ]; then
    lzop -c -d -q "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on lzop-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  elif [ "X$FILETYPE" = 'Xcomp' ]; then
    gzip -c -d -q "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on compress-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  elif [ "X$FILETYPE" = 'Xrar' ]; then
    rar2tarcat "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on rar-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  elif [ "X$FILETYPE" = 'Xzip' ]; then
    zip2tarcat "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on zip-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  elif [ "X$FILETYPE" = 'Xzsta' ]; then
    zstd -c -d -q "$NAME" | $ENGINE $BZIP3_OPTS > "$TMPFILE"
    if [ $? -ne 0 ]; then
      echo 'failed on zst-to-bzip3 conversion'
      rm -f "$TMPFILE"
      shift; continue
    fi
  else
    echo 'skipped on unrecognized filetype'
    rm -f "$TMPFILE"
    shift; continue
  fi

  # perform the final move, deleting old or new if necessary
  chmod --reference="$NAME" "$TMPFILE"
  mv "$TMPFILE" "$NEWNAME"
  if [ "$RETAINTIME" = 'y' ]; then
    touch --reference="$NAME" "$NEWNAME"
  fi
  S0=$(filesize "$NAME")
  S1=$(filesize "$NEWNAME")
  if [ $S1 -lt $S0 ]; then
    if [ "$DELORIG" != 'n' ]; then
      rm -f "$NAME"
      echo "$S0 vs. $S1 (deleting original)"
    else
      echo "$S0 vs. $S1"
    fi
  else
    if [ "$DELORIG" = 'n' ]; then
      echo "$S0 vs. $S1 (warning: new file is not smaller)"
    elif [ "$DELORIG" = 'y' ]; then
      rm -f "$NAME"
      echo "$S0 vs. $S1 (deleting original: new file is not smaller)"
    else
      rm -f "$NEWNAME"
      echo 'new file deleted (not smaller than old)'
    fi
  fi

  shift
done
