#!/bin/bash

# awsread, txt to speech using cloudy aws polly

# needs aws, mpv, xclip?, md5sum, awk

# usage:
# awsread                       # will read from clipboard
# awsreas /path/to/file.txt     # will read from file

# config
voices=(Amy Brian Joanna Matthew Kendra Joey)
#disabled Ivy Justin Emma Kimberly Salli
#aws polly describe-voices --language-code en-GB ; aws polly describe-voices --language-code en-US
resume="please" # non-empty is true
keepAudio=""    # non-empty is true
# split text at (max:3000)
bytes="600"
# text display width in chars (50-80)
width="50"
# center text block
centertext="mkay" # non-empty is true
# storeroot
storeroot="$HOME/tmp/recAWS"
mkdir -p "$storeroot" || exit
# debug 1 is true
debug="0"
# end config

storeInit (){
    # hash based on file path
    hash="$(readlink -f "$input" | md5sum | awk '{print $1}')"
    (( debug )) && echo "$hash"
    store="$storeroot/${hash}/"
    mkdir -p "$store" || exit
    touch "$store" || exit
}

resumeSave (){
    if [[ "$base" != "clipboard" ]]; then
        echo "$part" > "$store/resume"
        echo "$base" > "$store/name"
        echo "$fullpath" >> "$store/name"
    fi
}

# padding to center
calcPad(){
    if [[ -n $centertext ]]; then
        columns="$(tput cols)"
        pad="$((( columns - width )/2))"
    else
        pad="0"
    fi
}

# calc percentage (will need perc2 script to actually display that?)
perc () {
    local tmp
    tmp="$(bc -l <<< "$1/$2*100")"
    awk 'BEGIN{printf "%."'"2"'"f\n", "'"$tmp"'"}'
}

help () { 
cat << EOF
awsread, txt to speech using cloudy aws polly
needs aws (free validated aws account), mpv, xclip?, md5sum, awk
-----------------------------------------------------------------
usage:
awsread                     # will read from clipboard
awsreas file.txt            # will read from file
awsread --goto 100 file.txt # or -g goto page 100, will set resume to this new entry
awsread --list              # or -l list over store
awsread --random            # or -r read random page
awsread --hash ab           # or -x will read first file that includes those letters in hash

interactive keys:
q                           # skip to next block (mpv)
space                       # toggle playback (mpv)
ctrl+c                      # press for exit and to get the normal cursor back

config:
'resume' is enabled by default and should work for files only, not for clipboard.
'keepAudio' is disabled by default, if enabled it will keep ogg-vorbis audio files in configured directory.
For more, see the header section of the script.
EOF
}

# switches
# help && exit
if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then # print help
    help
    exit
fi

# goto 
if [ "$1" == "--goto" ] || [ "$1" == "-g" ]; then # goto chunk
    shift
    jumpto="$1" # assuming --goto 120
    shift
fi

# list && exit
if [ "$1" == "--list" ] || [ "$1" == "-l" ]; then # list stored resumes/names ...
    cd "$storeroot" || exit
        # sort by date horror
        find . -type f -iname "name" -printf "%T@\t%Tc %h\n" | sort -n | cut -d " " -f 8- |\
        while read -r path; do
            name="$(tail -1 "$path/name")"
            from="$(cat "$path/resume")"
            printf -v from "% 4d" "$from" # pad with spaces
            path="$(basename "$path")" #&& path="${path:0:8}..." # clean and shorten path/hash
            echo -e "$path\t$from\t$name"
        done
    exit
fi

# random
if [ "$1" == "--random" ] || [ "$1" == "-r" ]; then # goto random page
    random="1" # 1 is true
    (( debug )) && echo "Random page requested"
    shift # assuming --random file.txt
fi

# hash
if [ "$1" == "--hash" ] || [ "$1" == "-x" ]; then # find file by hash
    shift
    search="$1"
    cd "$storeroot" || exit
    cd -- "$(find . -name "*$search*" -type d | tail -1)" || exit
    input="$(cat name | tail -1)" || exit
fi

# else just path to file
if [ -f "$1" ]; then
    input="$1"
fi

# start
# assuming we have a valid $input file path here, lets check again
if [ -f "$input" ]; then
    echo "$input"
    fullpath="$(readlink -f "$input")"
    (( debug )) && echo "$fullpath"
    # else its probably clipboard, checked again later in the code ˇ
fi

# storeInit
storeInit # gets done in any case
# hide cursor
tput civis

# checks
command -v aws >/dev/null 2>&1 || { echo "I need aws cli, exiting." >&2; exit 1; }
command -v awk >/dev/null 2>&1 || { echo "I need awk, exiting." >&2; exit 1; }
command -v mpv >/dev/null 2>&1 || { echo "I need mpv player, exiting." >&2; exit 1; }
command -v md5sum >/dev/null 2>&1 || { echo "I need md5sum, exiting." >&2; exit 1; }

cleanup () {
  (( debug )) && echo "$?"
  [ -n "$tmp" ] && rm -fr "$tmp"
  tput cnorm
}

# tmp dir
tmp="/tmp/$RANDOM-$$-awsread"
trap cleanup EXIT SIGTERM SIGINT 
# https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html
mkdir -m 700 "$tmp" || { echo '!! unable to create a tmp dir' >&2; tmp=; exit 1; }

# named pipe
pipe="$tmp/pipe"
if [[ ! -p $pipe ]]; then
    mkfifo $pipe
fi

# play
play (){
    if [[ -n $keepAudio ]]; then
        name="$(($(date +%s%N)/1000000))" # lets name parts by epoch miliseconds
        mpv --no-resume-playback --msg-level=all=no --no-video --record-file="${store}${name}.ogg" "$1"
    else
        mpv --no-resume-playback --msg-level=all=no --no-video "$1"
    fi
}

# read clipboard or file (assuming text)
if [ "$#" -eq  "0" ]
then
    # store clipboard to tmp (assuming text)
    xclip -selection clipboard -o > "$tmp/some.txt" 2>/dev/null || powershell.exe Get-clipboard > "$tmp/some.txt" || exit 1
    base="clipboard" 
else
    # file
    [[ -f "$input" ]] || exit 1
    cp "$input" "$tmp/some.txt"
    # base
    baseext=${input##*/}  # file.ext
    base="${baseext%.*}"  # file
    # resume
    if [[ -n $resume ]] && ((! random )); then
        if [[ -n $jumpto ]]; then # jumpto is set, assuming --goto was used
            echo "Goto $jumpto"
        elif [[ -f "$store/resume" ]]; then
            jumpto="$(cat "$store/resume")"
        else
            jumpto="1"
        fi
        (( jumpto > 1 )) && echo "Resume from part $jumpto"
    fi

fi

# tr remove newlines, sed add newlines where punctuations are, 
# sed remove double spaces, split by some bytes, keep lines
cd "$tmp" || exit
cat "some.txt" | tr '\r\n' ' ' | sed 's/[.!?]  */&\n/g' | sed 's/ \{1,\}/ /g' | split --line-bytes="${bytes}" || exit 1

# count generated files
all="$(find . -type f -name "x*" | wc -l)"
(( random )) && randomPage="$(( RANDOM % all ))"
(( debug )) && echo "all $all, randomPage $randomPage"

# read
part="0"
for file in x*; do
    ((part=part+1))
    # random
    if (( random )); then
        (( part < randomPage )) && continue
        resume="" # just to skip next section
    fi
    # resume
    if [[ -n $resume ]]; then
        (( part < jumpto )) && continue
    fi
    (( random )) || resumeSave # store position if random is false
    calcPad # How much to pad to center block
    rand="(($RANDOM % ${#voices[@]}))"
    echoBlock (){
        echo
        # print header and text part
        (( all > 1 )) && echo "(${part}/${all})" 
        cat "$file" | fmt -w ${width} | sed 's/ \{1,\}/ /g' 
        echo "─── (voice ${voices[$rand]}) ─── ${perc}%" 
    }
    perc="$( perc "$part" "$all" )"
    echoBlock | pr -T -o "$pad"
    # perc2 "$perc" # < to much stuff on screen

    # replace text that is ofen read wrong, like 'dr.'
    sed -i 's/Dr\./Doctor/' "$file"
    sed -i 's/Mr\./Mister/' "$file"
    sed -i 's/Ms\./Miss/' "$file"

    # synth
    aws polly synthesize-speech \
    --output-format ogg_vorbis \
    --voice-id "${voices[$rand]}" \
    --text "$(cat "$file")" "$tmp/pipe" | play "$tmp/pipe" 2>/dev/null || exit
 
done