#! /bin/zsh

# This file is maintained at http://git.mdcc.cx/draai

# draai - Joost van Baal-Ilić's music playing stuff

# see also
#   joostvb@kovalevskaya:~% grep mpc .zshrc_local

# copyright:
COPYRIGHT='Copyright (C) 2006 - 2011 Joost van Baal, 2011 - 2013 Joost van Baal-Ilić'

# This program is freely distributable per the following license:
LICENSE="
Draai 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 of the License, or (at your option) any later
version.  This program is distributed WITHOUT ANY WARRANTY.  You should have
received a copy of the GNU General Public License along with draai.  If not,
see <http://www.gnu.org/licenses/>."

VERSION='20131212'

prog=`basename $0`

setopt extendedglob

# FIXME: default, rc, env, commandline
test -r $HOME/.draai/rc && . $HOME/.draai/rc

debug=${DR_DEBUG:-false}
shuffle=${DR_SHUFFLE:-true}
timestamp=${DR_TIMESTAMP:-true}
raw=${DR_RAW:-false}
sloppy=${DR_SLOPPY:-false}

# FIXME used in dr_watch(1) only
# sleep=${DR_SLEEP:-2}

# mpd.conf:
# metadata_to_use        "artist,album,title,track,name,genre,date,composer,performer,disc,comment"
rawformat="%position%) %file%: [[%artist% - ]%album% - %title% ][(%comment%)]([%name% - ][%genre% - ][%date% - ][%composer% - ][%performer% - ][%disc% - ][%track% ]%time%)"
format="[%position%) ][[[%artist% - ]%album% - %title%]|[%file%[ - %name%][ - %title%]]] (%time%)"
# format="[%position%) ][[[%artist% - ]%album% - %title%]|[%file%]] ([%name% - ]%time%)"
# draai list | grep '()'

crossfade=${DR_CROSSFADE:-4}
peek=${DR_PEEK:-30}
seek=${DR_SEEK:-20}

volume_step=${DR_VOLUME_STEP:-10}

fadeout_step=${DR_FADEOUT_STEP:-10}
fadeout_repeat=${DR_FADEOUT_REPEAT:-10}
fadeout_sleep=${DR_FADEOUT_SLEEP:-0.2}

syslog_file=${DR_SYSLOG_FILE:-/var/log/syslog}
syslog_facility=${DR_SYSLOG_FACILITY:-user}
syslog_level=${DR_SYSLOG_LEVEL:-info}

usage()
{

cat <<EOT
Usage: $prog [option [option ...]] command [track [track ...]|tracknumber [tracknumber ...]]

Play audio tracks, using mpc(1).

  -V, --version         show program's version number and exit
  -h, --help            show this help message and exit
  -l, --license         show license and exit

  -d, --debug
    Be very verbose.
  -S, --noshuffle
    Do not shuffle tracks and leave random mode untouched (default is: do
    shuffle and disable random mode).  See also the script dr_unsort.
  -p PLAYLIST, --playlist=PLAYLIST
    Playlist file; option can be supplied more than once.  To be used
    with command "draai".
  -r, --raw
    Print raw stuff, suitable for postprocessing (if combined with tail, peek
    or list).
  -s, --sloppy
    Do not try hard to make everything sound smooth.  If combined with skip:
    risk a squeak on old hardware.
  -t TIME, --time=TIME
    If combined with commands quit or draai: time at which to quit or start.

commands:

  crescendo:
    Play louder.

  delete:
    Delete track(s) from current playlist. Last arguments should be
    _positions_ of tracks to be removed, not filename.  If no position is
    given, deletes upcoming track from playlist.

  diminuendo:
    Play softer.

  draai:
    Play music: the files given as args and playlists passed via --playlist.

  fastforward:
    Seek forward in current track.

  guestlist:
    Reschedule listed tracknumbers as upcoming tracks. Last arguments should
    be the positions of tracks to be put on the guestlist, not filenames.

  harder:
    Play louder.

  init:
    Start a bunch of X terminals setting up some draai jobs: tail, logtail,
    watch, syslog, ...

  insert:
    Add file(s) (given either as args or on stdin) to playlist and schedule it as upcoming track.
    You probably want to have 'draai insert' read from a pipe fed by 'draai search'.

  list:
    List current playlist.

  logtail:
    Run tail(1) on system log file.

  move:
    Move tracknumber to tracknumber given as last argument.

  osjittisstil:
    Run this if unwanted silence pops up during a playing session (for now, it
    runs draai --sloppy skip).

  peek:
    Show status of current song, and show upcoming $peek FIXME tracks.

  quit:
    quit

  search:
    Search for substrings in all song tag types (artist, title, comment, filename,
    ...).  Similar to 'mpc search any <query>'.  You'll typically want to pipe
    this commands output to e.g. 'draai insert'.

  skip:
    Skip this track, start playing the next one now.

  syslog:
    Send raw information from tail to syslog.  You likely want to run "draai
    syslog" in the background.

  tail:
    Print information about track when it starts playing, similar to
    tail -f on a logfile.

  unguestlist:
    Reschedule listed tracknumbers to end of playlist.

  watch:
    Run watch(1) on "draai peek".

  zachter:
    Play softer.


EOT

#  -T, --notime          if combined with tail: do not add timestamps
#                          FIXME: not yet implemented
#  -n N, --number=N      number of tracks FIXME: not yet implemented

}

# help2man ./draai | man -l -

fadeout()
{
    # make it fade out (mpc does _not_ support that out of the box!)
    # we could add a "silent" song as last and use crossfade
    # we however use mpc's volume control.

    # as a sideeffect, sets $volume to original volume

    # % mpc volume
    # volume: 87%
    volume=$(mpc volume)
    volume=${volume#volume: }
    # 87%
    volume=${volume%\%*}
    # 87

    if [[ ! $volume =~ 1?[0-9]?[0-9] ]]
    then
        volume=100
    fi

    step=$(( $volume / $fadeout_step ))

    ## repeat 20
    repeat $fadeout_repeat
    do
       # -5
       mpc volume -$step > /dev/null
       # GNU coreutils
       sleep $fadeout_sleep
    done

##  exponential fadeout:
##  don't do that, it's way too fast
#
##        # 1024 = 2^10
##        step=$(( $vol / 1024 ))
##        mpc volume -$step > /dev/null
##        sleep 0.2
##        repeat 8
##        do
##          mpc volume -$step > /dev/null
##          step=$(( step * 2 ))
##          sleep 0.2
##        done
}

# calculate position of current track
position()
{
    for l in $(mpc | grep playing)
    do
        # zshexpn(1) "FILENAME GENERATION"
        if [[ $l == (#b)\#(*)/* ]]
        then
            position=$match
        fi
    done
    $debug && echo >&2 $prog: set position to \'$position\'
}

# see zsh-betabuiltins(1) for getopts help
# see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=384014 for manpage
# http://zsh.dotsrc.org/Doc/Release/zsh_16.html#SEC89
# getopt(1) understands long options. use that one?

# dpigs(1) has nice getopt(1) example

OPTS=$(getopt -o VhdSp:n:t:Trsl --long version,help,debug,noshuffle,playlist:,number:,time:,notime,raw,sloppy,license -- "$@")
eval set -- "$OPTS"

# define playlists to be an (ordinary) array
typeset -a playlists
playlists=()

while true; do
    case "$1" in
      -V|--version)
        echo $prog $VERSION
        exit 0
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      -q|--quiet)
        quiet=true
        shift
        ;;
      -d|--debug)
        debug=true
        shift
        ;;
      -S|--noshuffle)
        shuffle=false
        shift
        ;;
      -p|--playlist)
        playlists=($playlists "$2")
        shift 2
        ;;
      -n|--number)
        n="$2"
        shift 2
        ;;
      -t|--time)
        time="$2"
        shift 2
        ;;
      -T|--notime)
        timestamp=false
        shift
        ;;
      -r|--raw)
        raw=true
        shift
        ;;
      -s|--sloppy)
        sloppy=true
        shift
        ;;
      -l|--license)
        echo $COPYRIGHT
        echo $LICENSE
        exit 0
        ;;
      --)
        shift
        break
        ;;
      *)
        usage
        exit 1
        ;;
    esac
done

shift $(($OPTIND - 1))

# define args to be an associative array
typeset -A args

command=$1
shift

$debug && echo >&2 $prog: peek is \'$peek\'

case $command in
  draai)
    # no other args:
    # drDefault(plfiles=options.playlists, n=options.n, shuffle=options.shuffle)
    # some files present:
    # drDraai(args, n=options.n, shuffle=options.shuffle)
    set -A files "$@"
    if test -n "$time"
    then
        opts=
        $debug && opts="--debug"
        $shuffle || opts="$opts --noshuffle"
        test -n "$n" && opts="$opts --number $n"
        if test -n "$playlists"
        then
            for playlist in $playlists
            do
                opts="$opts --playlist $playlist"
            done
        fi
        if [[ $time = (#b)+(<->) ]]
        then
            # fixme: more strict match
            echo draai $opts draai $files | at now + $match minutes
        else
            echo draai $opts draai $files | at $time
        fi
    else
        # mpc clear > /dev/null
        mpc crop > /dev/null
        $debug && echo >&2 "$prog: gonna add $files"
        for f in $files
        do
            mpc add "$f"
            $debug && echo >&2 $prog: adding \'$f\'
        done
        # for some reason, piping to mpc with  ... echo $f; done | mpc add ...  fails

        for playlist in $playlists
        do
            mpc load "$playlist" | grep -v volume
        done
        $shuffle && mpc shuffle > /dev/null && mpc random off > /dev/null
        mpc crossfade $crossfade > /dev/null
        mpc play
    fi
    ;;
  watch)
    dr_watch draai peek
    ;;
  init|genesis)
    # user should be permitted to write on tty12. on Debian systems, this
    # means being a member of group tty
    draai tail >/dev/tty12 &
    draai syslog &

    if test -n "$DISPLAY"
    then
       # FIXME dr_watch()
       x-terminal-emulator -e "dr_watch draai peek" &
       x-terminal-emulator -e "draai logtail" &
       draai tail
    else
      # use "screen improved" by some debian guy

      # screen draai tail
      # FIXME dr_watch()
      screen -S draai -U dr_watch draai peek

      # "create"
      # exec

      # DR_PEEK=10 screen -S draai -U dr_watch draai peek 
      # screen -p draai -X c
      # screen -R  (debian-specific)
      # -d -m
    fi

    ;;
  next|volgende)
    # drNext(plfile=drDefaultNextPLFile, n=options.n)
    # next($args)
    echo not yet implemented
    ;;
  guestlist)
    # FIXME we assume all args are >> current position
    if test $# -gt 0
    then
        position=0
        position
        shift=0
        for p in "$@"
        do
            echo $p
        done | sort --numeric-sort --reverse | while read p
        do
            position=$(($position + 1 ))
            p=$(( $p + $shift ))
            $debug && echo >&2 "$prog: gonna move $p to $position"
            mpc move $p $position
            shift=$(( $shift + 1 ))
        done
    else
        echo "$prog: command guestlist needs tracknumber(s)"
        exit 1
    fi
    ;;
  unguestlist|unfriend|ontvriend)
    if test $# -gt 0
    then
        position=$( mpc playlist | tail -1 | cut -d\) -f1 )
        for p in "$@"
        do
            echo $p
        done | sort --numeric-sort --reverse | while read p
        do
            $debug && echo >&2 "$prog: gonna move $p to $position"
            mpc move $p $position
        done
    else
        echo "$prog: command unguestlist needs tracknumber(s)"
        exit 1
    fi
    ;;
  insert|voegin)
    if test $# -gt 0
    then
        set -A files "$@"
        $debug && echo >&2 $prog: gonna add \'$files\'
        for f in $files
        do
            $debug && echo >&2 $prog: gonna add \'$f\'
            mpc add "$f"
            position=$( mpc playlist | tail -1 | cut -d\) -f1 )
            draai guestlist $position
        done
    else
        # reads from stdin
        mpc insert
    fi
    ;;
  move)
    if test $# -eq 2
    then
         mpc move "$@"
    else
        echo "$prog: command move needs 2 tracknumbers"
        exit 1
    fi
    ;;
  search|zoek)
    mpc search any "$@"
    ;;
  nice)
    # drNice(args[1:], plfiles=options.playlists, n=options.n, shuffle=options.shuffle)
    echo not yet implemented
    ;;
  skip|slaover)
    # skip current song
    $sloppy || fadeout
    mpc next

    # FIXME: is NOT sleeping really "sloppy"!?
    $sloppy || sleep $fadeout_sleep

    # restore original volume
    $sloppy || mpc volume $volume >/dev/null
    ;;
  osjittisstil)
    draai --sloppy skip
    ;;
  delete)
    if test $# -gt 0
    then
        for p in "$@"
        do
            echo $p
        done | sort --numeric-sort --reverse | while read p
        do
            mpc del $p
        done
    else
        position=0
        position
        mpc del $(( $position + 1 ))
    fi
    ;;
  fastforward|voorwaarts)
    mpc seek +$seek%
   ;;
  crescendo|harder|louder)
    mpc volume +$volume_step
   ;;
  diminuendo|zachter|softer)
    mpc volume -$volume_step
   ;;
  tail|staart)
    # tail: like tail -f

    # FIXME use variables for 2 and 74 e.a.
    old=""
    while sleep 2; do
        current=$(mpc --format $format | head -1)
        $raw && string=$(mpc --format $rawformat | head -1)
        if test "$old" != "$current"
        then
            if $raw
            then
                echo $string
            else
                echo -n $(date +%H:%M)
                # 80 - 5 = 75 columns left: 1 for space, 3 for dots, 41 + 30 for rest
                # see zshmisc PROMPT EXPANSION / EXPANSION OF PROMPT SEQUENCES

                # %G     Within a %{...%} sequence, include a `glitch': that
                #        is, assume that a single character width will be
                #        output.

                # c With ${#name}, count the total number of characters in an
                #   array, as if the elements were concatenated with spaces
                #   between them.

                # %>string> Specifies truncation behaviour for the remainder
                #        of the prompt string. 

                # Sun 18 17:09 < Fruit> $#foo
                # Sun 18 17:10 < Fruit> maar dat is stiekem stuk
                # Sun 18 17:10 < Fruit> want dat geeft je het aantal bytes
                # Sun 18 17:10 < Fruit> ipv het aantal karakters

                # only truncate if $current > 74 chars.
                # if <= 74 chars: line out to left
                if [[ $#current  -gt 74 ]]
                then
                    # FIXME fails when playing from radio station
                    print -P " %41>>$current%>>...%30<<$current"
                else
                    # parse current: Guitar - Mind The Gap Volume 61 - Tokyo Memory (5:19)
                    # split off timespec: rightadjust
                    # time is "12:16)"
                    time=$(echo $current | sed 's/.*(//')
                    track=$(echo $current | sed 's/^\(.*\)(.*$/\1/')

                    # we assume time is not wider than (234:67)
                    # AFX - Analord 08 - Backdoor.Spyboter.A         (125:06)
                    #     -66-                                        ( -7-
                    printf " %-66.66s%8.8s\n" "$track" "($time"
                fi
            fi
            old=$current
        fi
    done
    # alternatively, one might like to run
    #   watch --no-title 'mpc --format "[[[%artist% - ]%album% - %title%]|[%file%]] ([%name% - ][%track% ]%time%)"'
    ;;
  logtail)
    tail -F $syslog_file | grep ' draai\['
    ;;
  syslog)
    draai --raw tail | logger -i -p $syslog_facility.$syslog_level -t draai
    ;;
  list)
    # list current playlist
    $raw && format=$rawformat
    mpc --format $format playlist
    ;;
  peek|spiek)
    # show status of current song
    mpc | grep playing
    # show upcoming $peek tracks
    position=0
    position
    total=$(mpc playlist | wc -l)
    $raw && format=$rawformat

    $debug && echo >&2 $prog: peek is \'$peek\'

    mpc --format $format playlist | tail -$(( $total - $position + 1 )) | head -$peek | \
      while read current
    do
        time=$(echo $current | sed 's/.*(//')
        track=$(echo $current | sed 's/^\(.*\)(.*$/\1/')
        if [[ $#track -gt 74 ]]
        then
            if [[ $track == \>* ]]
            then
                print -nP "%47>>$track%>>...%22<<$track"
            else
                print -nP " %46>>$track%>>...%22<<$track"
            fi
        else
            if [[ $track == \>* ]]
            then
                printf "%-72.72s" "$track"
            else
                printf " %-71.71s" "$track"
            fi
       fi
       # we assume time is not wider than (234:67)
       printf "%8s\n" "($time"
    done
    ;;
  play|unpause|resume|ga)
    if test -n "$time"
    then
        if [[ $time = (#b)+(<->) ]]
        then
            # fixme: more strict match
            echo draai play | at now + $match minutes
        else
            echo draai play | at $time
        fi
    else
        mpc play
    fi
    ;;
  quit|exit|stop|pause|ho)
    if test -n "$time"
    then
        if [[ $time = (#b)+(<->) ]]
        then
            # fixme: more strict match
            echo draai quit | at now + $match minutes
        else
            echo draai quit | at $time
        fi
    else
        fadeout
        if [[ "$command" == pause ]]
        then
            mpc pause
        else
            mpc stop > /dev/null
        fi
        # restore original volume
        mpc volume $volume > /dev/null
    fi
    ;;
  *)
    usage
    exit 1
    ;;
esac

