#!/usr/bin/env python3
from __future__ import print_function
#Released under GNU General Public License version 3, see the file COPYING for details.
#An attempt at a speech-friendly alsa mixer which runs in the console (no X-windows)
import alsaaudio, sys, os
"""
  The commands module was dropped in python 3, but its getstatusoutput function was added to the subprocess module in python3
  getstatusoutput however is not in subprocess module of python2 and it does not seem to be importable from __future__.
  We therefore test the major python version and then import getstatusoutput from commands in python2 and from subprocess in python3
"""

major_version=sys.version_info.major
if major_version==2:
  from commands import getstatusoutput
if major_version==3:
  from subprocess import getstatusoutput
  
def cls():
  #run the shell command, clear to clear the screen
  cl=os.system("clear")
  return

def set_increment(vol_range):
  if vol_range[1]<16:
    increment=int(float(100)/float(vol_range[1]))
  else:
    increment=5
  return increment
  

def contain_vol(vol):
  if vol<0:
    vol=0
    sys.stdout.write(chr(7))
  if vol>100:
    vol=100
    sys.stdout.write(chr(7))
  return vol
  
  
def new_volume(mixer, voltype):
    current_vol=mixer.getvolume(voltype)

    vol=current_vol[0]
    if isinstance(vol, str):
      vol=int(vol.rstrip('L'))
    return vol
def vol_value(vol):
  volval=vol[0]
  if isinstance(volval, str):
    volval=volval.rstrip('L')
  return int(volval)  

def get_mixerlist(cardnumber):
  mixers=[]
  mixer_names=[]
  mixer_ids=[]
  try:
    mixers=alsaaudio.mixers(cardnumber)
  except Exception as e:
    err=prompt('Failed to get mixer list for card '+str(cardnumber)+' Press enter to continue')
  if len(mixers)==0:
    err=prompt('Failed to get mixer list for card '+str(cardnumber)+' Press enter to continue')
    return False
  for i in mixers:
    dups=mixers.count(i)
    if dups==1:
      #There is only one of these, so copy its name and give it a mixer id of 0 and go on.
      mixer_names.append(i)    
      mixer_ids.append(0)
      continue
    mid=0
    while dups>0:
      try:
        m=alsaaudio.Mixer(i, mid, cardnumber)
        name=i+' index '+str(mid)
        if name not in mixer_names:
          mixer_names.append(name)
          mixer_ids.append(mid)
      except alsaaudio.ALSAAudioError:
        pass
      mid=mid+1
      dups=dups-1
  return (mixers, mixer_names, mixer_ids)
  
      
def change_mixer(mixerlist, m):
  mixers=mixerlist[0]
  mixer_names=mixerlist[1]
  mixer_ids=mixerlist[2]
  cls()
  try:
    mixer=alsaaudio.Mixer(mixers[m], mixer_ids[m], card)
  except Exception as e:
    err=prompt("Failed to get the details for "+mixers[m]+". \nThe error was: "+str(e)+"\n Press enter to continue")
    return False
    
  extra_info=''
  choices=[]
  instruction='Pick a control to change on '+mixer_names[m]+':'
  
  #can mixer change volume?
  
  v=mixer.volumecap()
  if len(v)>0:
    #We know there is volume capability, but not if it is Playback or Capture or both
    #Get the allowed range so that we can change the increment if required
    #First try specificly for playback volume by getting its range.
    try:
      pr=mixer.getrange(0)
    except alsaaudio.ALSAAudioError:
      pr=False
    #Now try specificly for capture volume by getting its range.
    try:
      cr=mixer.getrange(1)
    except alsaaudio.ALSAAudioError:
      cr=False
    if pr:
      #set playback increment
      p_increment=set_increment(pr)
      pv=mixer.getvolume(0)
      pv=vol_value(pv)
      extra_info=extra_info+'\n can change playback volume'
      choices.append('playback volume down from '+str(pv)+' percent')
      choices.append('playback volume up from '+str(pv)+' percent')
    if cr:
      c_increment=set_increment(cr)
      cv=mixer.getvolume(1)
      cv=vol_value(cv)
      extra_info=extra_info+'\n can change capture volume'
      choices.append('capture volume down from '+str(cv)+' percent')
      choices.append('capture volume up from '+str(cv)+' percent')    

  capabilities=' '.join(mixer.switchcap())
  try:  
    mute=mixer.getmute()
  except alsaaudio.ALSAAudioError:
    mute=[]
#  if 'Capture' in capabilities:
#    #There is a chance that recording can be switched on/off
  try:
    record=mixer.getrec()
    can_record=record[0]
    extra_info=extra_info+'\ncan toggle recording on/off'
    if can_record==0:
      choices.append('recording off')
    else:
      choices.append('recording on')
  except alsaaudio.ALSAAudioError:
    record=[]
    can_record='n'
  if len(mute)>0:
    extra_info=extra_info+'\ncan mute/unmute '
    if mute[0]==0:
      choices.append('mute '+mixer_names[m])
    if mute[0]==1:
      choices.append('unmute '+mixer_names[m])
  #See if the mixer can be set to different options (enum)
  enums=mixer.getenum()
  if len(enums)==2:
    options=enums[1]
    opt_selected=enums[0]
    opt=options.index(opt_selected)
    instruction=instruction+'\n '+mixer_names[m]+' can be set to one of '+str(len(options))+' options\n'
    can_be_set=True
    choices.append('set '+mixer_names[m]+' to another value, now '+opt_selected)
  else:
    can_be_set=False
    
    
  choice=0
  while choice!='q':
    choice=select_from(choices, instruction, choice, extra_info)
    if choice=='q':
      continue
    if choices[choice].startswith('playback volume down'):
      pv=pv-p_increment
      pv=contain_vol(pv)
      mixer.setvolume(pv, -1, 0)
      pv=new_volume(mixer, 0)
      choices[choice]='playback volume down from '+str(pv)+' percent'
      choices[choice+1]='playback volume up from '+str(pv)+' percent'
      continue

    if choices[choice].startswith('playback volume up'):
      pv=pv+p_increment
      pv=contain_vol(pv)
      mixer.setvolume(pv, -1, 0)
      pv=new_volume(mixer, 0)
      choices[choice-1]='playback volume down from '+str(pv)+' percent'
      choices[choice]='playback volume up from '+str(pv)+' percent'
      continue

    if choices[choice].startswith('capture volume down'):
      cv=cv-c_increment
      cv=contain_vol(cv)
      mixer.setvolume(cv, -1, 1)
      cv=new_volume(mixer, 1)
      choices[choice]='capture volume down from '+str(cv)+' percent'
      choices[choice+1]='capture volume up from '+str(cv)+' percent'
      continue
    if choices[choice].startswith('capture volume up'):
      cv=cv+c_increment
      cv=contain_vol(cv)
      mixer.setvolume(cv, -1, 1)
      cv=new_volume(mixer, 1)
      choices[choice-1]='capture volume down from '+str(cv)+' percent'
      choices[choice]='capture volume up from '+str(cv)+' percent'
      continue
    if choices[choice]=='recording on':
      mixer.setrec(0)
      choices[choice]='recording off'
      continue
    if choices[choice]=='recording off':
      mixer.setrec(1)
      choices[choice]='recording on'
      continue
      
    if choices[choice].startswith('mute '):
      mixer.setmute(1)
      choices[choice]='unmute '+mixer_names[m]
      continue
    if choices[choice].startswith('unmute '):
      mixer.setmute(0)
      choices[choice]='mute '+mixer_names[m]
      continue
    if can_be_set and choices[choice].startswith('set '):
      opt=select_from(options, 'Select another option to set '+mixer_names[m]+' to', opt)
      if opt!='q':
        mixer.setenum(opt)
        opt_selected=options[opt]
        choices[choice]='set '+mixer_names[m]+' to another value, now '+opt_selected
        continue
  return
def prompt(output):
  sys.stdout.write(str(output))
  sys.stdout.flush()
  key=getstatusoutput("catchkey")[1]
  print ('')
  return key
def select_from(entries, instruction='', current_entry=0, extra_info='No extra information available.'):
  tot_entries=len(entries)
  key=''
  active_keys=['enter', 'up', 'down', 'f1', 'q', 'Q']
  last_key=''
  have_seen=0
  result_list=[]
  cls()
  instruction=instruction+'\nThere are '+str(len(entries))+' choices:\n f1 for more info,\nup/down arrows to scroll, enter to select, q to quit.'
  print (instruction)
  while key!='q':
    last_key=key
    key=prompt (entries[current_entry])
    if key=='Q':
      print ('Bye-bye')
      exit()
    if key not in active_keys and key!=last_key:
      result_list=[]
      have_seen=0
      for j in entries:
        if key.lower()==j[0].lower():
          result_list.append(entries.index(j))
    if have_seen>=len(result_list):
      have_seen=0
    if len(result_list)>0 and key not in active_keys:
      current_entry=result_list[have_seen]
      have_seen=have_seen+1
      continue
      
    if key=='f1':
      print(extra_info)
    if key=='enter':
      return current_entry
    if key=='down':
      current_entry=current_entry+1
    if key=='up':
      current_entry=current_entry-1
    if current_entry==len(entries):
      current_entry=0
      cls()
      print (instruction)
    if current_entry<0:
      current_entry=len(entries)-1  
  return 'q'

def get_cards():
  #returns a tuple with a list of card names and a list with thecorresponding card numbers
  cards=[]
  cardnumbers=[]
  try:
    cardindex=alsaaudio.card_indexes()
  except Exception as e:
    err=prompt("Failed to find the details for your soundcards. \nThe error was: "+str(e)+"\n Press enter to continue")
    return False
  indexlen=len(cardindex)
    
  for c in cardindex:
    cards.append(alsaaudio.card_name(c)[0]+' card number '+str(c))
    cardnumbers.append(c)
    
  return (cards, cardnumbers)
  
  

#main program starts here  
card_details=get_cards()
cards=card_details[0]
cardnumbers=card_details[1]

if not cards:
  exit(1)

card=0
if len(cards)>1:
  card=select_from(cards, 'pick a sound card')
else:
  card=0

while card!='q':
  mixerlist=get_mixerlist(cardnumbers[card])
  if mixerlist==False:
    err=prompt("Failed to find the mixers for "+cards[card]+". \n Press enter to continue")
    card=select_from(cards, 'pick a sound card', card)
    continue
  else:
    mixers=mixerlist[0]
    mixer_names=mixerlist[1]
    mixer_ids=mixerlist[2]
  if len(mixers)==0:
    err=prompt('There are no mixers available on card '+cards[card]+' with card index '+str(cardnumbers[card])+' press enter to continue')
    card=select_from(cards, 'pick a sound card', card)
    continue
      
  mix=0
  while mix!='q':
    mix=select_from(mixer_names, 'Pick a mixer on '+cards[card], mix)
    if mix=='q':
      continue
    change_mixer(mixerlist,mix)
  card_details=get_cards()
  cards=card_details[0]
  cardnumbers=card_details[1]
  
  if len(cards)>1:
    if card>=len(cards):
      card=0
      print('The available soundcards must have changed!')
    card=select_from(cards, 'pick a sound card', card)
  else:
    card='q'
    
print ("Bye-bye!")

