#!/bin/bash

# There is no GPIO0 or GPIO1
# Zero padding means p_list[2] = GPIO2
p_list=("0" "0" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27")
p_locs=(0 0 3 5 7 29 31 26 24 21 19 23 32 33 8 10 36 11 12 35 38 40 15 16 18 22 37 13)
p_cache=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) # Enabled/not
p_dir=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) # In/out
p_stat=(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0) # Powered/unpowered

root=/sys/class/gpio

p_id=0  # Pin ID
p_en=0  # Pin enabled
p_in=0  # Pin dir (in=1/out=0)

messages=() # max 4 lines
latestdatest=""

## Colors
c_pre="\e[38;5;"
c_default="${c_pre}15m"
c_disabled="${c_pre}244m"
c_dim="${c_pre}33m"
c_off="${c_pre}202m"
c_on="${c_pre}118m"
c_in="${c_pre}45m"
c_pink="${c_pre}207m"
c_purple="${c_pre}141m"
c_warn="${c_pre}11m"
c_ok="${c_pre}10m"
c_err="${c_pre}9m"
rst="\e[0m"
##

trap ctrlc INT
function ctrlc() {
 tput cub 99
 echo "`tput setaf 1`ctrl+c detected - exiting`tput sgr0`"
 tput cnorm
 exit 1
}

function log() {
 upthedate
 local words="[ ${latestdatest} ] ${1}"
 local sev="$2"
 local logs=${#messages[@]}
 local maxlen=78
 local logcolor="${c_default}"
 case $sev in
  err) logcolor="${c_err}";;
  warn) logcolor="${c_warn}";;
  ok) logcolor="${c_ok}";;
 esac
 if [ $logs -ge 4 ]; then # Prune first message
  # A loop would be smarter, but this is quicker
  messages[0]=${messages[1]}
  messages[1]=${messages[2]}
  messages[2]=${messages[3]}
  messages[3]="${logcolor}${words:0:$maxlen}${rst}"
 else
  messages[$logs]="${logcolor}${words:0:$maxlen}${rst}"
 fi
}

function upthedate(){
 latestdatest=`date "+%_I:%M:%S %P"`
}

function showlogs() {
 local line=23
 for i in `seq ${#messages[@]} -1 0`; do
  tput cup $line 2
  tput el
  [ -z "${messages[$i]}" ] || echo -en "${messages[$i]}"
  let line=line-1
 done
}
function hidelogs() {
 local line=22
 for i in `seq 1 4`; do
  tput cup $line 0
  tput el
  let line=line-1
 done
}
function invalidpin(){
 log "GPIO$1 is not a valid GPIO pin" "warn";showlogs
}
# Takes pin
# Returns enabled y/n
function checkpin() {
 p_id=$1
 [[ "$p_id" == *"gpio"* ]] && p_id=${p_id:4}
 if [ -d $root/gpio${p_id} ]; then
  p_en=1
  p_cache[$p_id]=1
  if [[ `cat $root/gpio${p_id}/direction` == "in" ]]; then
   p_in=1
  else
   p_in=0
   p_stat[$p_id]=`cat $root/gpio${p_id}/value`
  fi
  p_dir[$p_id]=$p_in
 else
  zeroes $p_id
 fi
}

function zeroes(){
 p_id=$1
 p_en=0
 p_in=0
 p_cache[$p_id]=0
 p_dir[$p_id]=0
 p_stat[$p_id]=0
}

function enablepin() {
 [ -z "$1" ] && return
 [[ $1 -gt 27 || $1 -lt 2 ]] && invalidpin $1 && return
 p_id=$1
 if [ ${p_cache[$p_id]} -eq 1 ]; then
  log "Pin gpio$p_id is already enabled" "warn";showlogs
  return;
 fi
 echo $p_id > $root/export
 log "Enabling pin gpio$p_id" "ok";showlogs
 sleep .05;setpindir "$p_id" "o"
}

function disablepin() {
 [ -z "$1" ] && return
 [[ $1 -gt 27 || $1 -lt 2 ]] && invalidpin $1 && return
 p_id=$1
 if [ ${p_cache[$p_id]} -eq 0 ]; then
  log "Pin gpio$p_id is already disabled" "warn";showlogs;return
 fi
 echo $p_id > $root/unexport
 zeroes $p_id
 log "Disabling pin gpio$p_id" "ok";showlogs
}

function turnonpin() {
 [ -z "$1" ] && return
 [[ $1 -gt 27 || $1 -lt 2 ]] && invalidpin $1 && return
 p_id=$1
 if [ ! -d $root/gpio${p_id} ]; then
  log "Pin gpio$p_id is not enabled" "warn";showlogs;return
 fi
 if [ ${p_stat[$p_id]} -eq 1 ]; then
  log "Pin gpio$p_id is already powered" "warn";showlogs;return
 fi
 [[ ${p_cache[$p_id]} -eq 1 && ${p_dir[$p_id]} -eq 0 ]] && echo 1 > $root/gpio${p_id}/value
 log "Powering pin gpio$p_id" "ok";showlogs
}

function turnoffpin() {
 [ -z "$1" ] && return
 [[ $1 -gt 27 || $1 -lt 2 ]] && invalidpin $1 && return
 p_id=$1
 if [ ! -d $root/gpio${p_id} ]; then
  log "Pin gpio$p_id is not enabled" "warn";showlogs;return
 fi
 if [ ${p_stat[$p_id]} -eq 0 ]; then
  log "Pin gpio$p_id is already off" "warn";showlogs;return
 fi
 [[ ${p_cache[$p_id]} -eq 1 && ${p_dir[$p_id]} -eq 0 ]] && echo 0 > $root/gpio${p_id}/value
 log "Unpowering pin gpio$p_id" "ok";showlogs
}

function setpindir() {
 [ -z "$1" ] && return;[ -z "$2" ] && return
 [[ $1 -gt 27 || $1 -lt 2 ]] && invalidpin $1 && return
 p_id=$1;p_dir=$2
 if [ ! -d $root/gpio${p_id} ]; then
  log "Pin gpio$p_id is not enabled" "warn";showlogs;return
 fi
 case $p_dir in
  o) echo out > $root/gpio${p_id}/direction;log "Setting pin gpio$p_id as output" "ok";showlogs;;
  i) echo in > $root/gpio${p_id}/direction;log "Setting pin gpio$p_id as input" "ok";showlogs;;
  *) log "$p_dir is not a valid direction" "warn";showlogs;;
 esac
}

function refreshpins() {
 if [ ! -d $root ]; then
  tput rmcup;echo "`tput setaf 1`No gpio detected. Send help.`tput sgr0`";exit 1
 fi

 for _pin in `ls $root`; do # Check for enabled pins
  if [[ "$_pin" == "gpio"* && "$_pin" != *"chip"* ]]; then
   checkpin $_pin
  fi
 done

 for _pin in ${p_list[@]}; do # Check for disabled pins
  if [ ${p_cache[$_pin]} -eq 1 ]; then
   [ -d $root/gpio${_pin} ] || zeroes $_pin
  fi
 done
}

function printkey() {
 tput cup 0 2
 echo -en "Color key:"
 tput cup 0 16
 echo -en "${c_disabled}pin disabled${rst}"
 tput cup 0 33
 echo -en "${c_in}pin in${rst}"
 tput cup 0 45
 echo -en "${c_on}pin out -> on${rst}"
 tput cup 0 63
 echo -en "${c_default}pin out -> off${rst}"
}

function display() {
tput sc
printkey
tput civis
# tput cup down over
tput cup 2 2
 for i in `seq 0 ${#p_list[@]}`; do
  _p=${p_list[i]}
  [[ "$_p" == "0" || -z $_p ]] && continue
  [[ "$_p" == "15" ]] && tput cup 3 2
  [ ${p_cache[i]} -eq 0 ] && _status=$c_disabled || _status=$c_default
  [[ ${p_cache[i]} -eq 1 && ${p_dir[i]} -eq 1 ]] && _status=$c_in
  [[ ${p_cache[i]} -eq 1 && ${p_dir[i]} -eq 0 && ${p_stat[i]} -eq 1 ]] && _status=$c_on
  echo -en "${_status}"
  [[ ${#_p} -lt 2 ]] && echo -n "[ $_p]" || echo -n "[$_p]"
  echo -en "${rst}  "
 done
 echo
tput rc
}

function printmenu() {
 tput civis
 local opts="$1"
 local oc="$2"
 local line=$3
 tput cup $line 0
 # https://stackoverflow.com/a/28104488
 IFS=,
 menubottom=$line
 for opt in $opts; do
  tput cuf $oc
  echo "${opt}"
  let menubottom="menubottom+1"
 done
 unset IFS
}

function monitorpins() {
 local mon
 clearmenu 17 0
 hidelogs
 tput cup 5 2
 echo -en "${c_dim}Monitoring pins${rst}"
 tput cup 6 2
 echo -en "${c_disabled}Press q to stop${rst}"
 #local loading=(".  " ".. " "..." ".. ")
 local loading=(".  " " . " "  ." " . ")
 local lindex=0
 while [[ "$mon" != "q" ]]; do
  echo -en "${c_disabled}${loading[$lindex]}${rst}"
  read -n1 -t1 -s mon
  refreshpins;display
  tput cup 6 17
  let lindex=lindex+1
  [ $lindex -ge ${#loading[@]} ] && lindex=0
 done
 showlogs
 clearmenu 6 0
}

function mainmenu() {
 tput civis
 tput cup 5 4
 echo -e "${c_pink}-=-=-= Main Menu =-=-=-${rst}"
 _over=3
 availablemainmenuoptions="e) enable pin,d) disable pin,p) power pin,u) unpower pin,i) set pin direction,r) refresh display,m) monitor gpio,h) help,q) quit"
 printmenu "$availablemainmenuoptions" "$_over" 7
 mymenuspot=$menubottom
 let mymenuspot="mymenuspot+1"
 tput cup $mymenuspot $_over
 echo -n "> "
 tput el
 tput cnorm
 read -n1 mainmenuopt
 case $mainmenuopt in
  e) echo -en "${c_dim}nable${rst}";enablemenu;mainmenu;;
  d) echo -en "${c_dim}isable${rst}";disablemenu;mainmenu;;
  p) echo -en "${c_dim}ower${rst}";powermenu;mainmenu;;
  u) echo -en "${c_dim}npower${rst}";unpowermenu;mainmenu;;
  i) tput cub1;echo -en "${c_dim}d${rst}i${c_dim}rection${rst}";directmenu;mainmenu;;
  r) echo -en "${c_dim}efresh${rst}";refreshpins; display;mainmenu;;
  m) echo -en "${c_dim}onitor${rst}";monitorpins;mainmenu;;
  h) helppls;mainmenu;;
  q) tput rmcup;exit 0;;
  *) log "$mainmenuopt is not a valid menu choice" "warn";showlogs;mainmenu;;
 esac
 tput rmcup
}

function helppls(){
 clearmenu 17 0;hidelogs;tput cup 0 0;tput el;tput civis
 tput cup 5 0;echo -en "\e[38;5;219m";tput rev
 echo -n "                         "
 echo -n "GPIO Monitor & Control Script"
 echo -n "                          "
 tput sgr0
 tput cup 6 18;echo -en "${c_pink}-=-=-= Help menu and about this tool =-=-=-${rst}"
 tput cup 8 0
cat << EOF
   This tool was designed to be able to monitor and control, in real-time,
   the GPIO pins on my pi zero without needing to write a special python
   script for a quick test or check.

   The script functions as explained below:

   All menu options are single letters and are accepted without needing
   to press enter. The only time enter is required is when multiple
   characters may be necessary (e.g. entering a pin number).

   This program comes with no warranty and only interacts with the GPIO
   pins on the pi. Anything connected to those pins is not the
   responsibility of this script.

   Enjoy <3
EOF
 read -n1 -s
 tput cup 5 0;tput el
 tput cup 6 0;tput el
 clearmenu 18 0;hidelogs;showlogs;printkey
}

function clearmenu(){
 tput civis
 local spot=5
 local menu=0
 [ -z $2 ] && menu=40
 local max=$1
 let max=max+1
 [ $max -gt 19 ] && max=19
 while [ $spot -lt $max ]; do
  tput cup $spot $menu
  tput el
  let spot=spot+1
 done
}
function gpiodisclaimer() {
 local offset=$1;local alpha;local beta
 let alpha=8+offset
 let beta=9+offset
 tput cup $alpha 40;echo -en "This is the GPIO# not the physical"
 tput cup $beta 40;echo -en "pin location."
}
function enablemenu() {
 tput cup 5 40
 echo -en "${c_purple}-=-=-= Enable Pin =-=-=-${rst}"
 tput cup 7 40;echo -en "Enter the pin ID you wish to enable."
 gpiodisclaimer
 tput cup 11 40;echo -n "> "
 tput cnorm
 read enpin
 enablepin $enpin
 clearmenu 11;refreshpins;display
}

function disablemenu() {
 tput cup 5 40
 echo -en "${c_purple}-=-=-= Disable Pin =-=-=-${rst}"
 tput cup 7 40;echo -en "Enter the pin ID you wish to disable."
 gpiodisclaimer
 tput cup 11 40;echo -n "> "
 tput cnorm
 read dispin
 disablepin $dispin
 clearmenu 11;refreshpins;display
}

function powermenu() {
 tput cup 5 40
 echo -en "${c_purple}-=-=-= Power Pin =-=-=-${rst}"
 tput cup 7 40;echo -en "Enter the pin ID you wish to turn on."
 gpiodisclaimer
 tput cup 11 40;echo -n "> "
 tput cnorm
 read powapin
 turnonpin $powapin
 clearmenu 11;refreshpins;display
}

function unpowermenu() {
 tput cup 5 40
 echo -en "${c_purple}-=-=-= Unpower Pin =-=-=-${rst}"
 tput cup 7 40;echo -en "Enter the pin ID you wish to turn off."
 gpiodisclaimer
 tput cup 11 40;echo -n "> "
 tput cnorm
 read powapin
 turnoffpin $powapin
 clearmenu 11;refreshpins;display
}

function directmenu() {
 tput cup 5 40
 echo -en "${c_purple}-=-=-= Change Direction =-=-=-${rst}" #   | LAST
 tput cup 7 40;echo -en "Enter the pin ID you wish to set the"
 tput cup 8 40;echo -en "direction of. This is the GPIO# not"
 tput cup 9 40;echo -en "the physical pin location."
 tput cup 11 40;echo -n "> "
 tput cnorm
 read dirpin
 clearmenu 11
 tput cup 5 40
 echo -en "${c_purple}-=-=-= Change Direction =-=-=-${rst}" #   | LAST
 tput cup 7 40;echo -en "Enter the direction you would like this"
 tput cup 8 40;echo -en "pin to go in."
 tput cup 10 40;echo -n "o) output"
 tput cup 11 40;echo -n "i) input"
 tput cup 13 40;echo -n "> "
 tput cnorm
 read -n1 dirdir
 setpindir $dirpin $dirdir
 clearmenu 13;refreshpins;display
}

tput smcup
refreshpins
display
mainmenu