/*
 * sndctl  -  a plugin for the Video Disk Recorder
 *        file: alsa.c
 * description: handling of the Alsa API
 *
 *      author: Thomas Hildebrandt <toxym@web.de>
 *
 * inspired by and reengineered from 'avolctl'
 * thanks to Martin Prochnow <nordlichtl@martins-kabuff.de>
 */

#include "defaults.h"
#include "alsa.h"

/*********************************************************
 * member functions for class cAlsa
 *********************************************************/

/*
 * constructors
 */
cAlsa::cAlsa(){
  // the following part is nearly complete from the 'avolctl' plugin
  // up to now I don't really understand, why these things must be done
  if( !snd_mixer_open( &handle, 0 )){
    dsyslog( "sndctl (cAlsa::cAlsa): snd_mixer_open() okay" );
    if( !snd_mixer_attach( handle, "default" )){
      dsyslog( "sndctl (cAlsa::cAlsa): snd_mixer_attach() okay" );
      if( !snd_mixer_selem_register( handle, NULL, NULL )){
        dsyslog( "sndctl (cAlsa::cAlsa): snd_mixer_selem_register() okay" );
        if( !snd_mixer_load( handle ))
          dsyslog( "sndctl (cAlsa::cAlsa): snd_mixer_load() okay" );
        else
          esyslog( "sndctl (cAlsa::cAlsa): ERROR: snd_mixer_load() failed!" );
      }else
        esyslog( "sndctl (cAlsa::cAlsa): ERROR: snd_mixer_selem_register() failed!" );
    }else
      esyslog( "sndctl (cAlsa::cAlsa): ERROR: snd_mixer_attach() failed!" );
  }else
    esyslog( "sndctl (cAlsa::cAlsa): ERROR: snd_mixer_open() failed!" );

  // set number of controls
  nrOfControls = CountControls();

  // create list of controls
  if( nrOfControls > 0 )
    if( controls = new struct control[nrOfControls] ){
      // get hardware values for every control
      UpdateControls();
      dsyslog( "sndctl (cAlsa::cAlsa): List of controls created." );
    } else {
      esyslog( "sndctl (cAlsa::cAlsa): ERROR! Can't allocate memory for '%d' controls.", nrOfControls );
      return;
    }
  else
    dsyslog( "sndctl (cAlsa::cAlsa): WARNING! No controls found. cMixer is uninitialized!" );
}

/*
 * destructor
 */
cAlsa::~cAlsa(){
  snd_mixer_close( handle );
}

/*
 * returns the number of controls for this mixer device
 */
int cAlsa::CountControls( void ){
  int               count;
  snd_mixer_elem_t *elem;
  long              min, max;

  // do not count again, if already done
  if(( count = nrOfControls ) < 0 ){
    // start with 0 controls
    count = 0;
    for( elem = snd_mixer_first_elem( handle ); elem ; elem = snd_mixer_elem_next( elem ))
      if( snd_mixer_selem_is_active( elem ) &&
          ( snd_mixer_selem_has_playback_volume( elem ) ||
            snd_mixer_selem_has_playback_switch( elem ))){
        // initialize 'min' and 'max' with undangerous values
        min = max = -1;

        // get real min and max
        snd_mixer_selem_get_playback_volume_range( elem, &min, &max );

        // check for a senseful range
        if( min == 0 && max > min && max > 10 )
          count++;
      }
  }

  dsyslog( "sndctl (cAlsa::CountControls): Found '%d' controls.", count );

  return count;
}

/*
 * checks for a mixer with a given name
 */
bool cAlsa::IsMixer( string Name ){
  snd_mixer_elem_t *elem;

  // search in all mixers for 'Name'
  for( elem = snd_mixer_first_elem(handle); elem; elem = snd_mixer_elem_next( elem )){
    if(( strcmp(snd_mixer_selem_get_name( elem ), Name.c_str()) == 0 ) &&
         snd_mixer_selem_is_active( elem ) &&
         ( snd_mixer_selem_has_playback_volume( elem ) ||
         snd_mixer_selem_has_playback_switch( elem )))
      return true;
  }

  // nothing found
  return false;
}

/*
 * fills in hardware depending values for every mixer control
 */
void cAlsa::UpdateControls( void ){
  snd_mixer_elem_t *elem;
  struct control   *ctrl = controls;

  for( elem = snd_mixer_first_elem( handle ); elem ; elem = snd_mixer_elem_next( elem ))
    if( snd_mixer_selem_is_active( elem ) && ( snd_mixer_selem_has_playback_volume( elem ) || snd_mixer_selem_has_playback_switch( elem ))){
      // initialize 'ctrl->min' and 'ctrl->max' with undangerous values
      ctrl->min = ctrl->max = -1;

      // get min and max
      snd_mixer_selem_get_playback_volume_range( elem, &ctrl->min, &ctrl->max );

      // check for a senseful range
      if( ctrl->min == 0 && ctrl->max > ctrl->min && ctrl->max > 10 ){
        // get name
        ctrl->name = string( snd_mixer_selem_get_name( elem ));

        dsyslog( "sndctl (cAlsa::UpdateControls): '%s' updated with min=%ld and max=%ld", ctrl->name.c_str(), ctrl->min, ctrl->max );

        ctrl++;
      }
    }
}

/*
 * set the volume of all controls
 */
void cAlsa::Volumes( void ){
  snd_mixer_elem_t             *elem;
  int                           i;
  snd_mixer_selem_id_t         *sid;

  snd_mixer_selem_id_malloc( &sid );

  // loop over all controls
  for( i = 0; i < nrOfControls; i++ ){
    if( (controls + i)->volume >= 0 ){
      // get ID from the name
      snd_mixer_selem_id_set_name( sid, (controls + i)->name.c_str());
      // get control element
      if( elem = snd_mixer_find_selem( handle, sid )){
        // set volume
        snd_mixer_selem_set_playback_volume_all( elem, (controls + i)->volume );

        // unmute if muted
        if( snd_mixer_selem_has_playback_switch( elem ))
          snd_mixer_selem_set_playback_switch_all( elem, 1 );

        dsyslog( "sndctl (cAlsa::Volumes): Control '%s' set to '%ld'.", (controls + i)->name.c_str(), (controls + i)->volume );
      }
    }
  }

  snd_mixer_selem_id_free( sid );
}
