#!/usr/bin/env luajit

--[[ This block is what system V LSB expects.
### BEGIN INIT INFO
# Provides:          aataaj
# Required-Start:    $local_fs
# Required-Stop:
# X-Start-Before:    alsa-utils espeakup networking
# Default-Start:     S
# Default-Stop:
# Short-Description: Scan for audio devices.
# Description:       Scan for all audio devices and produce an
#                    asound.conf include file fragment.
### END INIT INFO
]]

local _ = require 'polygLua'
Version = '0.0 crap'

local function goAway()
    __[[#!/bin/bash
        echo "Uninstalling aataaj"
        update-rc.d aataaj.lua remove
        rm /etc/init.d/aataaj.lua
        rm /usr/local/bin/aataaj.lua
	rm -fr /usr/local/share/doc/aataaj
        ./PolygLua.lua -vvvv uninstall
    ]]:log():show():Do()
end

local cmd = ''
local options =
{
    start   = {help = 'Command to start the scanning process, for Sys V init.', func = function(self, options, a, args, i) cmd = 'start' end},
    restart = {start},
    ['force-reload'] = {start},
    status  = {help = 'Command to check the status of the scanning process, for Sys V init.', },
    stop    = {help = 'Command to stop the scanning process, for Sys V init.', },
    JACK    = {help = 'Command to start the JACK stuff, for users.',  func = function(self, options, a, args, i) cmd = 'JACK' end},
    STOP =
    {
	help = 'Command to stop the JACK stuff, for users.',
	func = function(self, options, a, args, i)
	    _.killEmAll{'qsynth'}
	    __[[#!/bin/bash
		a2j_control --stop
		sleep 2
		a2j_control --exit
		sleep 2
	    ]]:Do()
	    _.killEmAll{'alsa_in', 'alsa_out', 'zita-a2j', 'zita-j2a',  'aseqjoy', 'jack-plumbing'}
	    __[[#!/bin/bash
		sleep 2
		jack_control stop
		sleep 2
		jack_control exit
		sleep 2
	    ]]:Do()
	    _.killEmAll{'jmcore', 'qjackctl'}
	    -- Catia is python, and no easy way to kill it.
	    -- Also it keeps jackdbus alive, no matter how hard you kill it.
	    __'pkill -TERM -u $USER -f catia':Do()
	    __'sleep 2':Do()
	    _.killEmAll{'jackdbus', 'a2jmidid'}
	    os.exit(0)
	end
    },
    asoundrcPath = {help = 'Path to our config files and stuff.',	value = '/var/lib/aataaj', },
    asoundrc     = {help = 'Name of asoundrc file.',			value = 'asoundrc', },
    aliases      = {help = 'Aliases for audio devices.',		value = {}, },
    install =
    {
	help = 'Command to install aataaj.lua',
	func = function(self, options, a, args, i)
	    if 'root' ~= _.who then
		E'Need to be root user to install.'
	    else
		print('INSTALLING!!!')
		-- espeak-ng has an entirely undocumented way of speciyfying the output device.  aplay can't deal with it's output, so have to pipe it through sox.
		__[=[#!/bin/bash
		    echo "Installing aataaj"
		    ./PolygLua.lua -vvvv install
		    if [[ ! -h /usr/local/bin/aataaj.lua ]]; then
		      ln -s `pwd`/aataaj.lua /usr/local/bin/aataaj.lua
                    fi
		    if [[ ! -h /etc/init.d/aataaj.lua ]]; then
		      ln -s /usr/local/bin/aataaj.lua /etc/init.d/aataaj.lua
		    fi
		    mkdir -p /usr/local/share/doc/aataaj
		    pushd /usr/local/share/doc/aataaj
		    espeak-ng --stdout "Your "                                           | sox - your.wav     channels 2 rate 44100
		    espeak-ng --stdout " audio devices are - "                           | sox - devices.wav  channels 2 rate 44100
		    espeak-ng --stdout "Device number "                                  | sox - device.wav   channels 2 rate 44100
		    espeak-ng --stdout ", sub device "                                   | sox - sub.wav      channels 2 rate 44100
		    espeak-ng --stdout "zero"                                            | sox - zero.wav     channels 2 rate 44100
		    espeak-ng --stdout "one"                                             | sox - one.wav      channels 2 rate 44100
		    espeak-ng --stdout "two"                                             | sox - two.wav      channels 2 rate 44100
		    espeak-ng --stdout "three"                                           | sox - three.wav    channels 2 rate 44100
		    espeak-ng --stdout "four"                                            | sox - four.wav     channels 2 rate 44100
		    espeak-ng --stdout "five"                                            | sox - five.wav     channels 2 rate 44100
		    espeak-ng --stdout "six"                                             | sox - six.wav      channels 2 rate 44100
		    espeak-ng --stdout "seven"                                           | sox - seven.wav    channels 2 rate 44100
		    espeak-ng --stdout "eight"                                           | sox - eight.wav    channels 2 rate 44100
		    espeak-ng --stdout "nine"                                            | sox - nine.wav     channels 2 rate 44100
		    espeak-ng --stdout "ten"                                             | sox - ten.wav      channels 2 rate 44100
		    espeak-ng --stdout "eleven"                                          | sox - eleven.wav   channels 2 rate 44100
		    espeak-ng --stdout "twelve"                                          | sox - twelve.wav   channels 2 rate 44100
		    espeak-ng --stdout "thirteen"                                        | sox - thirteen.wav channels 2 rate 44100
		    espeak-ng --stdout "fourteen"                                        | sox - fourteen.wav channels 2 rate 44100
		    espeak-ng --stdout "fifteen"                                         | sox - fifteen.wav  channels 2 rate 44100
		    espeak-ng --stdout "sixteen"                                         | sox - sixteen.wav  channels 2 rate 44100
		    espeak-ng --stdout "Please type the device number you heard best - " | sox - best.wav     channels 2 rate 44100
		    popd
		    ln -s `pwd`/*.md /usr/local/share/doc/aataaj/
		    update-rc.d aataaj.lua defaults
		]=]:log():show():Do()
	    end
	    os.exit(0)
	end
    },
    uninstall =
    {
	help = 'Command to uninstall aataaj.lua',
	func = function(self, options, a, args, i)
	    if 'root' ~= _.who then
		E'Need to be root user to uninstall.'
	    else
		print('UNINSTALLING!!!')
		goAway()
	    end
	    os.exit(0)
	end
    },
    purge =
    {
	help = 'Command to purge aataaj.lua',
	func = function(self, options, a, args, i)
	    if 'root' ~= _.who then
		E'Need to be root user to purge.'
	    else
		print('PURGING!!!')
		goAway()
		__[[#!/bin/bash
		    rm -fr /var/lib/aataaj
		]]:log():show():Do()
	    end
	    os.exit(0)
	end
    },
}

_.parse(arg, options, 'aataaj')


-- CHANGE these to suit.
local GUI = 'qjackctl'
if _.runnable('catia') then GUI = 'catia' end

local speaker = 'espeak'
if _.runnable('espeak-ng') then speaker = 'espeak-ng' end

local numbers = {[0]='zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen'}


local Names = {}
local Cards = {}
local cnt = 0

print('Scanning for audio devices.')
local cards = __'ls -d1 /proc/asound/card[0-9]*':noErr():Do()
for  i,l in ipairs(cards.lines) do
    local f, e = io.open(l .. '/id', 'r')
    local g, h = io.open(l .. '/codec#0', 'r')	-- Test to see if it's a real audio device.  I think this is a real test.  lol
    local a, b = io.open(l .. '/stream0', 'r')	-- Test to see if it's a real audio device.  I think this is a real test.  lol
    if nil == f then print('Could not open ' .. l .. '/id - ' .. e) else
	if (nil ~= g) or (nil ~= a) then
	    local n = f:read('*a'):sub(1, -2)
	    Cards[n] = {path = l, name = n, devs = {}, captureDevs = {}, playbackDevs = {}, card=i}
	    cnt = cnt + 1
	    Names[cnt] = n
	    if 'Loopback' ~= Cards[n]['name'] then
		Cards[n]['capture'] = __('ls -d1 ' .. l .. '/pcm[0-9]*c*'):noErr():Do()
		for  j,c in ipairs(Cards[n]['capture'].lines) do
		    local m = c:match('.*pcm(%d+).*')
		    Cards[n]['captureDevs'][j] = m
		    Cards[n]['devs'][m] = m
----		    print('Found capture  DEVICE: ' .. Cards[n].card - 1 .. '\tSUB: ' .. m .. '  \t' .. Cards[n]['name'])
		end
		Cards[n]['playback'] = __('ls -d1 ' .. l .. '/pcm[0-9]*p*'):noErr():Do()
		for  j,p in ipairs(Cards[n]['playback'].lines) do
		    local m = p:match('.*pcm(%d+).*')
		    Cards[n]['playbackDevs'][j] = m
		    Cards[n]['devs'][m] = m
		    print('Found playback DEVICE number: ' .. Cards[n].card - 1 .. '\tSUB: ' .. m .. '  \t' .. Cards[n]['name'])
		    if 'JACK' ~= cmd then
--			print('\t\tALSA_CARD=' .. Cards[n].card - 1 .. ' ' .. speaker .. ' "Found playback device ' .. Cards[n].card - 1 .. ' : ' .. Cards[n]['name'] .. ' DEVICE: ' .. Cards[n]['playbackDevs'][j] .. ' ' .. m .. '"')
--			io.flush()
		    end
		end

		local iec = ''
		for k, m in ipairs(__("amixer -c " .. Cards[n].card - 1 .. " scontrols | grep 'IEC958' | cut -d ',' -f 2"):noErr():Do().lines) do
		    iec = iec .. 'IEC958,' .. m .. '  '
		end
--		if '' ~= iec then print('\t\t\t\t' .. iec) end
		iec = ''
		for k, m in ipairs(__("amixer -c " .. Cards[n].card - 1 .. " controls | grep ' Playback Volume' | cut -d ',' -f 1"):noErr():Do().lines) do
		    iec = iec .. 'PCM Playback Volume,' .. m .. '  '
		end
--		if '' ~= iec then print('\t\t\t\t' .. iec) end

	    end
	end
    end
end
print('')
io.flush()


print('Your  ' .. cnt .. '  audio devices are - ')
print('  being turned on and volume maxed - ')
for k = 0, cnt do
    -- Turn on IEC958 for everything, they seem to default to off, so can't hear them.
    for l, m in ipairs(__("amixer -c " .. k .. " scontrols | grep 'IEC958' | cut -d ',' -f 2"):noErr():Do().lines) do
	__("amixer -c " .. k .. " set IEC958," .. m .. " on"):Do()
    end
    -- Turn the volume to maximum for everything.
    for l, m in ipairs(__("amixer -c " .. k .. " controls | grep ' Playback Volume' | cut -d ',' -f 1"):noErr():Do().lines) do
	__("amixer -c " .. k .. " cset iface=MIXER,name='PCM Playback Volume," .. m .. "' 100% "):Do()
    end
end


local speak = function(card, subdevice, device, words, printIt, forkIt, ...)
    local files = {...}
    local s = ''
    if nil == subdevice then subdevice = '' elseif '' ~= subdevice then subdevice = ',' .. subdevice end
    if 0 == #files then
	local file = os.tmpname() .. '.wav'
	D('Creating temporary script file at ' .. file)
	s = '#!/bin/bash\n' .. speaker .. ' -d hw:' .. device .. subdevice .. ' "' .. words .. '"'
    else
	s = 'aplay -q --device=hw:' .. device .. subdevice
	for i, v in ipairs(files) do s = s .. ' /usr/local/share/doc/aataaj/' .. v .. '.wav' end
    end
    if printIt then print(words .. '\t' .. card) ; io.flush() end
    if forkIt then
	__(s):noErr():noOut():fork()
    else
	__(s):Do()
    end
end

-- TODO - Start with the device, then go through the sub devices.  Coz at boot time subs don't seem to work.
--		Likely that'll lock things up at "all speak at once" time.
if 'start' == cmd then

    print('  being spoken through all at once, then one at a time - ')
    print('Please type the device number you hear best - ')
    for  k,C in pairs(Cards) do
	for  j,c in ipairs(C['playbackDevs']) do
	    speak(C.name, C['playbackDevs'][j], C.card - 1, 'Your  ' .. cnt .. '  audio devices are - ', false, true, 'your', numbers[cnt], 'devices')
	end
    end
    -- TODO - should do a proper "wait for speakers to finish" here.  Have fork(write a file), think that's what :wait(file) does.
    __'sleep 6':Do()
    for  k,n in ipairs(Names) do
	C = Cards[n]
	for  j,c in ipairs(C['playbackDevs']) do
	    speak(C.name, C['playbackDevs'][j], C.card - 1, 'Device number ' .. C.card - 1 .. ', sub device ' .. C['playbackDevs'][j], false, false, 'device', numbers[C.card - 1], 'sub', numbers[tonumber(C['playbackDevs'][j])])
	end
	__'sleep 1':Do()
    end
    io.flush()

    for  k,C in pairs(Cards) do
	for  j,c in ipairs(C['playbackDevs']) do
	    speak(C.name, C['playbackDevs'][j], C.card - 1, 'Please type the device number you heard best - ', false, true, 'best')
	end
    end

    local choice = tonumber(io.read())	-- Lua has no way of just checking IF there is ANY input, so can't do "check if there was a keypress, continue if not".
    local ourCard = ''

    for  k,C in pairs(Cards) do
	if (C.card - 1) == choice then
	    print('Your choice is ' .. choice .. '  ' .. C.name)
	    ourCard = C.name
	end
    end



    __('mkdir -p ' .. options.asoundrcPath.value):Do()
    local a, e = io.open(options.asoundrcPath.value .. '/jack-plumbing', 'w')
    if nil == a then print('Could not open ' .. options.asoundrcPath.value .. '/jack-plumbing - ' .. e) else
	a:write([[
(connect "system:capture_1" "ploop:playback_1")
(connect "system:capture_2" "ploop:playback_2")
(connect "cloop:capture_1" "system:playback_1")
(connect "cloop:capture_2" "system:playback_2")
(connect "cloop:capture_1" "(.*)-in:playback_1")
(connect "cloop:capture_2" "(.*)-in:playback_2")

(connect "qsynth:left"  "(.*)-in:playback_1")
(connect "qsynth:right"  "(.*)-in:playback_2")
	    ]])
	a:close()
    end

    local outCard = function(a, C, j)
		a:write('pcm.' .. C['name'] .. j .. ' {\n')
		a:write('   type hw\n')
		a:write('   card ' .. C['name'] .. '\n')
		a:write('   device ' .. C['devs'][j] .. '\n')
		a:write('}\n')
		a:write('ctl.' .. C['name'] .. j .. ' {\n')
		a:write('   type hw\n')
		a:write('   card ' .. C['name'] .. '\n')
		a:write('   device ' .. C['devs'][j] .. '\n')
		a:write('}\n\n')
    end

    local a, e = io.open(options.asoundrcPath.value .. '/' .. options.asoundrc.value, 'w')
    if nil == a then print('Could not open ' .. options.asoundrcPath.value .. '/' .. options.asoundrc.value .. ' - ' .. e) else
	print('Writing suggested ALSA configuration file to ' .. options.asoundrcPath.value .. '/' .. options.asoundrc.value)
	if '' ~= ourCard then
	    outCard(a, Cards[ourCard], '0')	-- How the fuck is that a string?
	end
	for  i,C in pairs(Cards) do
	    for  j,c in pairs(C['devs']) do
		if C.name ~= ourCard then
		    outCard(a, C, j)
		end
	    end
	end
	a:write([[
#################################################################################################################################

# The complex way - https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge

# More custom version, but it didn't work for me.
# hardware 0,0 : used for ALSA playback
#pcm.loophw00 {
#  type hw
#  card Loopback
#  device 0
#  subdevice 0
#  format S32_LE
#  rate 48000
#}

# playback PCM device: using loopback subdevice 0,0
# Don't use a buffer size that is too small. Some apps 
# won't like it and it will sound crappy 

#pcm.amix {
#  type dmix
#  ipc_key 219345
#  slave {
#    pcm loophw00
##    period_size 4096
##    periods 2
#  }
#}

# software volume
#pcm.asoftvol {
#  type softvol
#  slave.pcm "amix"

#  control { name PCM }

#  min_dB -51.0
#  max_dB   0.0
#}


# for jack alsa_in: looped-back signal at other ends
#pcm.cloop {
#  type hw
#  card Loopback
#  device 1
#  subdevice 0
#  format S32_LE
#  rate 48000
#}

# hardware 0,1 : used for ALSA capture
#pcm.loophw01 {
#  type hw
#  card Loopback
#  device 0
#  subdevice 1
#  format S32_LE
#  rate 48000
#}

# for jack alsa_out: looped-back signal at other end
#pcm.ploop {
#  type hw
#  card Loopback
#  device 1
#  subdevice 1
#  format S32_LE
#  rate 48000
#}

# duplex device combining our PCM devices defined above
#pcm.aduplex {
#  type asym
#  playback.pcm "asoftvol"
#  capture.pcm "loophw01"
#}

# default device
#pcm.!default {
#  type plug
#  slave.pcm aduplex

#  hint {
#       show on
#       description "Duplex Loopback"
#  }
#}



# Generic method seems to work better.
# playback PCM device: using loopback subdevice 0,0
pcm.amix {
  type dmix
  ipc_key 219345
  slave.pcm "hw:Loopback,0,0"
}

# capture PCM device: using loopback subdevice 0,1
pcm.asnoop {
  type dsnoop
  ipc_key 219346
  slave.pcm "hw:Loopback,0,1"
}

# duplex device combining our PCM devices defined above
pcm.aduplex {
  type asym
  playback.pcm "amix"
  capture.pcm "asnoop"
}

# ------------------------------------------------------
# for jack alsa_in and alsa_out: looped-back signal at other ends
pcm.ploop {
  type plug
  slave.pcm "hw:Loopback,1,1"
}

pcm.cloop {
  type dsnoop
  ipc_key 219348
  slave.pcm "hw:Loopback,1,0"
}

# ------------------------------------------------------
# default device

pcm.!default {
  type plug
  slave.pcm "aduplex"
}
	    ]])
	a:close()
    end
elseif 'JACK' == cmd then
    print('')
    print('Start up JACK and friends.')
    print('jack_control')
    __[[jack_control start
        jack_control ds alsa]]:Do()
    --jack_control dps device hw:RIG,0
    while 0 ~= __'jack_control status':Do().status do
	print('Waiting for JACK')
	__'sleep 1':Do()
    end
    if nil ~= GUI then
	print(GUI)
	__(GUI):noErr():noOut():forkOnce()
    end
    if _.runnable'jack-plumbing' then
	print('jack-plumbing')
	__'jack-plumbing -o /var/lib/aataaj/jack-plumbing 2>/dev/null':fork()
    end
    if _.runnable'a2j_control' then
	-- Bridge ALSA ports to JACK ports.  Only handles MIDI.
	-- MIDI via a2jmidid.  The --ehw enables hardware ports as well, equal to using the seq MIDI drivare according to https://freeshell.de/~murks/posts/ALSA_and_JACK_MIDI_explained_(by_a_dummy_for_dummies)/
	--a2j_control actually starts a2jmidid.
	----a2jmidid -e -u &
	-- I think the jack_control start and my current alsa config means a2jmidid gets started anyway.  But seem to need this bit to get the joystick covered.
	print('a2j_control')
	__'a2j_control --ehw && a2j_control --start':Do()
--	print('sleep 2')
--	__('sleep 2'):Do()
	print("")
    end


    local AIN = 'alsa_in'
--    if _.runnable'zita-a2j' then AIN = 'zita-a2j' end
    local AOUT = 'alsa_out'
--    if _.runnable'zita-j2a' then AOUT = 'zita-j2a' end

    print('Basic ALSA sound devices converted to JACK.')
    for  k,C in pairs(options.aliases.value) do
	print('HW playback: ' .. C['name'] .. '\tDEVICE: ' .. C['dev'])
	__(AOUT .. ' -j ' .. C['name'] .. ' -d ' .. C['dev']):noOut():noErr():fork()
    end
    print('HW playback: cloop\tDEVICE: cloop')
    -- No idea why, cloop wont work with zita-a2j.
    __'alsa_in  -j cloop  -d cloop':noOut():noErr():fork()
    --__[[sleep 1
    --   jack_connect cloop:capture_1 system:playback_1o()
    --   jack_connect cloop:capture_2 system:playback_2]]:Do()
    print('HW playback: ploop\tDEVICE: ploop')
    __'alsa_out -j ploop  -d ploop':noOut():noErr():fork()
    --__[[sleep 1
    --   jack_connect system:capture_1 ploop:playback_1
    --   jack_connect system:capture_2 ploop:playback_2]]:Do()


    print('')

    print('Rest of ALSA sound devices converted to JACK.')
    for  k,C in pairs(Cards) do
	for  j,c in ipairs(C['playbackDevs']) do
	    print('HW playback: ' .. C['name'] .. '\tDEVICE: ' .. C['playbackDevs'][j])
	    __(AOUT .. ' -j ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in -d ' .. C['name'] .. C['playbackDevs'][j]):fork()
    --	__'sleep 1':Do()
    --	__('jack_connect cloop:capture_1 ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in' .. ':playback_1'):Do()
    --	__('jack_connect cloop:capture_2 ' .. C['name'] .. '_' .. C['playbackDevs'][j] .. '-in' .. ':playback_2'):Do()
	end
	for  j,c in ipairs(C['captureDevs']) do
	    print('HW capture: ' .. C['name'] .. '\tDEVICE: ' .. C['captureDevs'][j])
	    -- TODO - This looks like the source of the "Detected excessive timing errors, waiting 10 seconds." spam.  Shut it up for now, investigate later.
	    -- https://discourse.ardour.org/t/hdmi-speakers-are-silent-with-intermittent-noise/108563 last post might help.
	    __(AIN .. ' -j ' .. C['name'] .. '_' .. C['captureDevs'][j] .. '-out -d ' .. C['name'] .. C['captureDevs'][j]):noOut():fork()
	end
    end
    print('')

    if _.runnable('aseqjoy') then
	print('Scanning for joysticks.')
	local sticks = __'ls -1 /dev/input/js[0-9]*':noErr():Do()
	for  i,l in ipairs(sticks.lines) do
	    print('aseqjoy ' .. l)
	    -- Buttons switch to that numbered MIDI channel, defaults to 1.
	    -- Axis are mapped to MIDI controllers 10 - 15
	    -- -r means to use high resolution MIDI values.
	    __('aseqjoy -d ' .. l:sub(-1,-1) .. ' -r'):fork()
	end
	print('')
    end

    if _.runnable('jack-plumbing') then
	print('Stop our jack-plumbing, eventually.')
	__'sleep 4':Do()
	_.killEmAll{'jack-plumbing'}
    end

    if _.runnable('~/.aataaj_JACK.lua') then
	print('Running users aataaj_JACK.lua')
	__'~/.aataaj_JACK.lua':Do()
    end
end
