#!/bin/sh
# Version 0.1.5

# ########################################################################
# Instructions:
#    See http://intent-radio.smblott.org/playlist.html

# ########################################################################
# Constraints:
#    This script has to work with the standard Android shell, and with only
#    the utilities available on a vanilla Android installation (which
#    doesn't even include head and tail).
#
# For URL encoding we use sed, if it's available.  Otherwise, we use a
# simple shell function to replace the most important non-URL characters.

root='/sdcard/Tasker/.intent_radio'
state="$root/state.txt"
seendir="$root/seen"
randomfile="$root/random"
stopfile="$root/stop"
loopfile="$root/loop"
tmp="$state.tmp"
log="$root/log.txt"

# Bail early out early (to avoid even having to parse this script) if
# there's nothing to be done.
[ $# = 0 ] && exit 1
[ $# = 1 ] && ! [ -f "$state" ] && exit
[ $# = 1 ] && [ -f "$stopfile" ] && [ "$1" != 'next' ] && exit

mkdir -p "$root"
exec 4>> $log 2>&4

# For testing, it is convenient to be able to run this script on
# non-Android systems too.  ir_on_android succeeds only if calibre is not
# available.  calibre is unlikely to be installed on an Android device.
ir_on_android ()
{
   ! type calibre > /dev/null
}

log ()
{
   ir_on_android || echo "$*"
   date +"%H:%M.%S $*" >&4
}

log "args $*"

ir_cleanup ()
{
   rm -fr "$state" "$seendir" "$loopfile" "$randomfile" "$stopfile"
}

# ########################################################################
# Options.
# The vanilla shell on Android does not have getopt.

command="$1"
shift

opt_random=''
opt_loop=''

while [ 0 -lt $# ]
do
   case "$1" in
      "-r" ) opt_random='yes'; shift ;;
      "-l" ) opt_loop='yes'; shift ;;
      * ) break 2 ;;
   esac
done

# ########################################################################
# Identify file types.

ir_is_url ()
{
   case "$1" in
      file://*  ) true ;;
      http://*  ) true ;;
      https://* ) true ;;
      content://* ) true ;;
      * ) false ;;
   esac
}

ir_is_playlist ()
{
   case "$1" in
      *.m3u  ) [ -f "$1" ] ;;
      * ) false ;;
   esac
}

ir_is_audio ()
{
   case "$1" in
      *.mp3  ) [ -f "$1" ] ;;
      *.aac  ) [ -f "$1" ] ;;
      *.m4a  ) [ -f "$1" ] ;;
      *.ogg  ) [ -f "$1" ] ;;
      *.oga  ) [ -f "$1" ] ;;
      *.flac ) [ -f "$1" ] ;;
      *.wav  ) [ -f "$1" ] ;;
      * ) false ;;
   esac
}

# ########################################################################
# Take a text file and randomize the lines.
# It would be so much simpler if we had "sort -R".

ir_randomize ()
{
   if [ -n "$RANDOM" ] && type sort > /dev/null
   then
      log "randomizing..."
      file="$1"
      tmp_file_1="$file.1.rtmp"
      tmp_file_2="$file.2.rtmp"

      log "before randomizing... $file...:"
      cat "$file" >&4

      while read line
      do
         echo "$RANDOM $line"
      done < "$file" > "$tmp_file_1"

      sort -g "$tmp_file_1" > "$tmp_file_2"
      rm "$tmp_file_1"

      while read random line
      do
         echo "$line"
      done < "$tmp_file_2" > "$tmp_file_1"

      rm "$tmp_file_2"
      mv "$tmp_file_1" "$file"

      log "after randomizing...:"
      cat "$file" >&4

   else
      log "not randomizing... sort is not available"
   fi
}

# ########################################################################
# URL encoding.

ir_urlencode_sed ()
{
   # Everything except: s/\//%2f/g;
   # Was sed 's/%/%25/g; s/ /%20/g; s/\t/%09/g; s/!/%21/g; s/"/%22/g; s/#/%23/g; s/\$/%24/g; s/\&/%26/g; s/(/%28/g; s/)/%29/g; s/\*/%2a/g; s/+/%2b/g; s/,/%2c/g; s/-/%2d/g; s/\./%2e/g; s/:/%3a/g; s/;/%3b/g; s//%3e/g; s/?/%3f/g; s/@/%40/g; s/\[/%5b/g; s/\\/%5c/g; s/\]/%5d/g; s/\^/%5e/g; s/_/%5f/g; s/`/%60/g; s/{/%7b/g; s/|/%7c/g; s/}/%7d/g; s/~/%7e/g; s/      /%09/g' | sed "s/'/%27/g"
   sed '/^\// {s/%/%25/g; s/ /%20/g; s/\t/%09/g;s/"/%22/g; s/#/%23/g; s/\&/%26/g; s/(/%28/g; s/)/%29/g; s/\*/%2a/g; s/+/%2b/g; s/,/%2c/g; s/:/%3a/g; s/;/%3b/g; s/>/%3e/g; s/?/%3f/g; s/@/%40/g; s/\[/%5b/g; s/\\/%5c/g; s/\]/%5d/g; s/\^/%5e/g; s/`/%60/g; s/{/%7b/g; s/|/%7c/g; s/}/%7d/g; s/~/%7e/g}' \
      | sed "/^\// {s/'/%27/g}"
}

# Copy stdin to stdout, replacing every instance of "$1" withe "$2".
ir_replace ()
{
   symbol="$1"
   replacement="$2"

   while read line
   do
      done_first=''
      OLDIFS="$IFS"
      IFS="$symbol"
      for tok in $line
      do
         [ -n "$done_first" ] && echo -n "$replacement"
         echo -n "$tok"
         done_first='yes'
      done
      echo
      IFS="$OLDIFS"
   done
}

# URL encode, but only replacing "%" and " ".
ir_urlencode_poor ()
{
   ir_replace "%" "%25" \
      | ir_replace " " "%20"
}

ir_url_encode ()
{
   if type sed > /dev/null
   then
      ir_urlencode_sed
   else
      ir_urlencode_poor
   fi
}

ir_make_file_url ()
{
   while read f
   do
      case "$f" in
         /* ) echo "file://$f" ;;
         * ) echo "$f" ;;
      esac
   done
}

ir_urlencode_files ()
{
   ir_url_encode | ir_make_file_url
}

# ########################################################################
# Play.

IR_PLAY_INTENT='org.smblott.intentradio.PLAY'

ir_send_intent ()
{
   log ""
   echo "  " am broadcast -a "$@" >&4
   ir_on_android \
      && am broadcast -a "$@"
}

ir_play ()
{
   echo "$1"
   ir_send_intent $IR_PLAY_INTENT -e url "$1" -e name "$1"
}

# ########################################################################
# Play next item.

ir_next_picker ()
{
   while [ -f "$state" ] && [ -s "$state" ]
   do
      {
         read item
         cat >&3
      } < "$state" 3> "$tmp" && mv "$tmp" "$state"

      if [ -n "$item" ] && ir_is_url "$item"
      then
         ir_play "$item"
         return 0
      fi
   done
   return 1
}

ir_next ()
{
   ir_next_picker && return

   if [ -f "$loopfile" ]
   then
      cp "$loopfile" "$state"
      [ -f "$randomfile" ] && ir_randomize "$state"
      ir_next_picker && return
   fi

   ir_cleanup
}

# ########################################################################
# Construct playlists for various types of thing.
#
# Each function assumes:
#  - The current working directory is the location of the file or directory.
#  - It's first (and only) argument is the absolute path of the file or directory.
# Callers arrange this by using ir_cd.

ir_playlist ()
{
   while read f
   do
      case "$f" in
         \#* ) true ;;
         /* ) ir_process "$f" ;;
         * ) [ -n "$f" ] && ir_process "$(pwd)/$f" ;;
      esac
   done < "$1"
}

ir_directory ()
{
   dir="$1"
   ls | while read file
      do
         ir_process "$dir/$file"
      done
}

ir_file ()
{
   echo "$1"
}

# ########################################################################
# Because playlist and directory handling is recursive, it is possible to
# get stuck in an infinite loop.  Here, we provide tests to ensure that
# that cannot happen.

ir_see ()
{
   for thing
   do
      [ -d "$thing" ] && mkdir -p "$seendir/$thing"
      [ -f "$thing" ] && true > "$seendir/$thing"
   done
}

ir_seen ()
{
   [ -d "$seendir/$1" ] || [ -f "$seendir/$1" ]
}

# ########################################################################
# Change directory to that of the thing we're currently handling.

ir_absolute_path ()
{
   if type readlink > /dev/null
   then
      # Amazingly, we have readlink in /system/bin (at least on my phone).
      readlink -f "$1"
   else
      # If we don't have readlink, then we'll try building an absolute path by hand.
      case "$1" in
         /* ) echo $1 ;;
         * ) echo "$(pwd)/$1"
      esac
   fi
}

# The first argument is either a directory or a file.
# cd to the directory or (in the case of a file) the containing directory.
# Then call the remaining command/arguments with the absolute path of the first
# argument appended.
# This allows us to handle both absolute and relative paths uniformly.
# We assume that "$thing" exists.
ir_cd ()
{
   thing=$(ir_absolute_path "$1")
   shift

   if [ -f "$thing" ]
   then
      directory="${thing%/*}"
   else
      directory="$thing"
   fi

   if ! ir_seen "$thing"
   then
      ir_see "$directory" "$thing"
      if [ "$(pwd)" = "$directory" ]
      then
         # Don't create a new process and change directory if we don't need
         # to.
         "$@" "$thing"
      else
         (
            if cd "$directory"
            then
               "$@" "$thing"
            fi
         )
      fi
   else
      log "skipping $thing"
   fi
}

# ########################################################################
# Handle various types of thing.

ir_process ()
{
   for arg
   do
      ir_is_url "$arg" && echo $arg
      [ -d "$arg" ] && ir_cd "$arg" ir_directory
      ir_is_playlist "$arg" && ir_cd "$arg" ir_playlist
      ir_is_audio "$arg" && ir_cd "$arg" ir_file
   done
}

# ########################################################################
# External operations.

ir_append ()
{
   log "append $*"

   if [ -f "$stopfile" ]
   then
      rm "$stopfile"
   fi

   # rm -fr "$seendir"
   ir_process "$*" | ir_urlencode_files > "$tmp"

   if [ -f "$loopfile" ]
   then
      cat "$tmp" >> "$loopfile"
   fi

   cat "$tmp" >> "$state"
   rm "$tmp"

   if [ -f "$randomfile" ]
   then
      # We randomize the entire playlist, not just the newly appended part.
      # Is this the best thing to do?
      ir_randomize "$state"
   fi

   log "state..."
   cat "$state" >&4
   log "...end"

   ir_next
}

ir_start ()
{
   ir_cleanup

   [ -n "$opt_loop" ] && true > "$loopfile"
   [ -n "$opt_random" ] && true > "$randomfile"

   ir_append "$@"
}

ir_stop ()
{
   true > "$stopfile"
}

ir_resume ()
{
   [ -f "$stopfile" ] && rm "$stopfile"
   ir_next
}

case "$command" in
   'start'    ) ir_start "$@" ;;
   'append'   ) ir_append "$@" ;;
   'next'     ) ir_resume ;;
   'complete' ) ir_next ;;
   'stop'     ) ir_stop ;;
#  'resume'   ) ir_resume ;;
esac

true
