#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <linux/soundcard.h>

#define	VNC_INTERNAL_SPKR	0x01	//the sw mute on/off control bit
#define	VNC_INTERNAL_MIC	0x10	//the hw internal/handset mic bit
#define	VNC_LINE_OUT_MUTE	0x40	//the sw mute of line-out bit
#define	VNC_HANDSET_SW_ONLY	0x80	//software in total control of handset!

int mixer_fd = -1;
unsigned supported;
int source;
char **p;			/* parameters */

#define ALL_OPT "all"
#define MASTER_OPT "master"
#define DSP_OPT "dsp"
#define MIC_OPT "mic"
#define FM_OPT "fm"
#define SOURCE_OPT "source"
#define LINE_OPT "line"
#define PHONE_OPT "phone"	//LINE1 is our analog phone
#define SPKR_OPT "spkr"
#define RECLEV_OPT "reclev"
#define HELP_OPT "help"
#define RECMON_OPT "recmon"
#define PRIVATE_OPT "private"

#define NOT_CMP -2
#define GET -1
#define SET 0

#define LEFT  0x0001
#define RIGHT 0x0100
#define LEFT_MASK 0x00ff
#define RIGHT_MASK 0xff00

int
str_cmp (char *pattern, char *s)
{
  int i, j, k, l;

  if(!s[0])	/* if empty string - display all */
      return GET;

  if(s[0] == '-')
     s++;	/* skip over the leading - */
     
  for (i = 0; (pattern[i]) && (s[i]) && (pattern[i] == s[i]); i++);

  if (pattern[i])
    return NOT_CMP;	/* not this one */

  if (!s[i])	
    return GET;		/* if just keyword - get it */

  if (!s[i + 1])
    return GET;		/* if keyword= - display only */

  if (s[i + 1] == '(')		/* we got stereo */
  {
      for (k = i + 1; s[k] && (s[k] != ','); k++);

      if (!s[k])
				return GET;

      s[k] = 0;

      for (l = k + 1; s[k] && (s[k] != ')'); k++);

      s[k] = 0;

      j = strtol((s + i + 2), NULL, 0);
      l = strtol((s + k + 1), NULL, 0);

      return LEFT * j + RIGHT * l;
  }
  else	//no stereo settings
  {
      j = strtol((s + i + 1), NULL, 0);
      return (LEFT + RIGHT) * j;
  }
}

void
get_source (void)
{
  source = 0;
  if (ioctl (mixer_fd, SOUND_MIXER_READ_RECSRC, &source) == -1)
    perror ("Error reading mixer recording source");

  printf ("Record input:");
  if (source == 0 || source == 1 << SOUND_MIXER_SPEAKER)
    printf ("\tNot connected\n");
  else
  {
    if (source & ( 1 << SOUND_MIXER_MIC))
    {
	if (source & (1 << SOUND_MIXER_SPEAKER))
	    printf ("\tInternal microphone ");
        else
	    printf ("\tHandset Microphone ");
    }

    if (source & ( 1 << SOUND_MIXER_LINE))
      printf ("\tLine ");

    if (source & (1 << SOUND_MIXER_LINE1))
      printf ("\tAnalog phone ");

    printf("\n");
  }
}

void
set_source (int id)
{
  source = 0;
  if (ioctl (mixer_fd, SOUND_MIXER_READ_RECSRC, &source) == -1)
    perror ("Error reading mixer recording source");

  source ^= 1 << id;
  if (ioctl (mixer_fd, SOUND_MIXER_WRITE_RECSRC, &source) < 0)
    perror ("Mixer.set_source()");
}


int
check_source (char *s)
{
  int k;

  k = str_cmp (SOURCE_OPT, s);

  switch (k)

    {
    case GET:
      get_source ();
      return 1;

    case NOT_CMP:
      return 0;

    default:
//      set_source(SOUND_MIXER_LINE);
      set_source ((k) & 0x0F);	//convert from "stereo" to a number

      return 1;
    }

}

void
set_volume (int mixer_id, int volume)
{
  if (ioctl (mixer_fd, MIXER_WRITE (mixer_id), &volume) < 0)
    perror ("Mixer.set_mixer()");
}

int
get_volume (int mixer_id)
{
  int volume;

  if (ioctl (mixer_fd, MIXER_READ (mixer_id), &volume) < 0)
    {
      perror ("Mixer.get_volume()");
      return 0;
    }
  return volume;
}

char *
sget_volume (int mixer_id)
{
  char *s;
  int v;

  v = get_volume (mixer_id);
  s = NULL;
  s = calloc (100, sizeof (char));
  if (s == NULL)
    {
      perror ("Mixer.sget_volume()");
      return "ERROR";
    }
  if ((v & LEFT_MASK) == (v & RIGHT_MASK) >> 8)
  {
    sprintf (s, "\t%d", (v & LEFT_MASK));
  }
  else
  {
    sprintf (s, "\t(L=%d R=%d)", (v & LEFT_MASK), (v & RIGHT_MASK) >> 8);
  }
  return s;
}

int
check_master (char *s)
{
  int k;

  if ((supported & SOUND_MASK_VOLUME) == 0)
    return 0;
  k = str_cmp (MASTER_OPT, s);

  switch (k)
    {
    case GET:
      printf ("Master volume %s\n", sget_volume (SOUND_MIXER_VOLUME));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_VOLUME, k);
      return 1;
    }
}

int
check_mic (char *s)
{
  int k;

  if ((supported & SOUND_MASK_MIC) == 0)
    return 0;

  k = str_cmp (MIC_OPT, s);
  switch (k)
    {
    case GET:
      printf ("Mic/handset %s\n", sget_volume (SOUND_MIXER_MIC));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_MIC, k);
      return 1;
    }
}

int
check_reclev (char *s)
{
  int k;
  if ((supported & SOUND_MASK_RECLEV) == 0)
    return 0;
  k = str_cmp (RECLEV_OPT, s);
  switch (k)
    {
    case GET:
      printf ("Record volume %s\n", sget_volume (SOUND_MIXER_RECLEV));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_RECLEV, k);
      return 1;
    }
}

int
check_dsp (char *s)
{
  int k;
  if ((supported & SOUND_MASK_PCM) == 0)
    return 0;
  k = str_cmp (DSP_OPT, s);
  switch (k)
    {
    case GET:
      printf ("DSP volume %s\n", sget_volume (SOUND_MIXER_PCM));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_PCM, k);
      return 1;
    }
}

int
check_fm (char *s)
{
  int k;
  if ((supported & SOUND_MASK_SYNTH) == 0)
    return 0;
  k = str_cmp (FM_OPT, s);
  switch (k)
    {
    case GET:
      printf ("FM volume %s\n", sget_volume (SOUND_MIXER_SYNTH));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_SYNTH, k);
      return 1;
    }

}

int
check_recmon (char *s)
{
  int k;
  if ((supported & SOUND_MASK_IMIX) == 0)
    return 0;
  k = str_cmp (RECMON_OPT, s);
  switch (k)
    {
    case GET:
      printf ("RecMon volume %s\n", sget_volume (SOUND_MIXER_IMIX));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_IMIX, k);
      return 1;
    }
}


int
check_private (char *s)
{
  int k, mixprivate;

//  if ((supported & SOUND_MASK_IMIX) == 0)
//    return 0;
  k = str_cmp (PRIVATE_OPT, s);

  switch (k)
    {
    case GET:

      mixprivate = 0;
      if (ioctl (mixer_fd, MIXER_READ (SOUND_MIXER_PRIVATE2), &mixprivate) < 0)
      {
	printf("Error reading the private mixer control...\n");
        return 0;
      }
      printf ("Private [0x%X]", mixprivate);
	if (mixprivate & VNC_INTERNAL_SPKR)
	    printf("\tInt spkr OFF");
	else
	    printf("\tInt spkr ON");

	if (mixprivate & VNC_INTERNAL_MIC)
	    printf("  Int mic ON");
	else
	    printf("  Hset mic ON");

	if (mixprivate & VNC_LINE_OUT_MUTE)
	    printf("  Line out OFF");
	else
	    printf("  Line out ON");

	if (mixprivate & VNC_HANDSET_SW_ONLY)
	    printf("  Hset detection OFF\n");
	else
	    printf("  Hset detection ON\n");


      return 1;
    case NOT_CMP:
      return 0;

    default:
      mixprivate = k & 0xFF;
      printf ("Setting PRIVATE to 0x%X.\n", mixprivate);
      if (ioctl (mixer_fd, MIXER_WRITE (SOUND_MIXER_PRIVATE1), &mixprivate) < 0)
	{
	printf("Error setting the private mixer control...\n");
	return(0);
	}
      return 1;
    }
}

#if 0
int
check_line (char *s)
{
  int k;
  if ((supported & SOUND_MASK_LINE) == 0)
    return 0;
  k = str_cmp (LINE_OPT, s);
  switch (k)
    {
    case GET:
      printf ("LINE recmon volume %s\n", sget_volume (SOUND_MIXER_LINE));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_LINE, k);
      return 1;
    }
}

int
check_phone (char *s)
{
  int k;
  if ((supported & SOUND_MASK_LINE1) == 0)
    return 0;
  k = str_cmp (PHONE_OPT, s);
  switch (k)
    {
    case GET:
      printf ("PHONE recmon volume %s\n", sget_volume (SOUND_MIXER_LINE1));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_LINE1, k);
      return 1;
    }
}

int
check_spkr (char *s)
{
  int k;
  if ((supported & SOUND_MASK_SPEAKER) == 0)
    return 0;
  k = str_cmp (SPKR_OPT, s);
  switch (k)
    {
    case GET:
      printf ("SPKR (built-in mic) volume %s\n",
	      sget_volume (SOUND_MIXER_SPEAKER));
      return 1;
    case NOT_CMP:
      return 0;
    default:
      set_volume (SOUND_MIXER_SPEAKER, k);
      return 1;
    }
}
#endif

void 
check_all (char *s)
{
  check_master (s);
  check_dsp (s);
  check_fm (s);
//  check_line (strdup (s));
//  check_phone (strdup (s));
//  check_spkr(strdup(s));
  check_mic (s);
  check_recmon (s);
  check_reclev (s);
  check_source (s);
  check_private (s);
}

void 
usage (char *s)
{
  printf ("SYNOPSIS\n");
  printf ("\t%s [options]\n\n", s);
  printf ("OPTIONS\n");
//  printf("\t-all\t\tshow all settings\n");
  printf ("\t-master=n\tset master volume to n\n");
  printf ("\t-dsp=n\t\tset dsp synth volume to n\n");
  printf ("\t-mic=n\t\tset mic preamp to n\n");
  printf ("\t-fm=n\t\tset fm synth volume to n\n");
  printf ("\t-source={6,7,14} set source to {Line, Mic/Handset, Phone} (XOR)\n");
//  printf ("\t-line=n\t\tset line volume to n\n");
//  printf ("\t-phone=n\t\tset phone volume to n\n");
//  printf("\t-spkr=n\t\tset speaker volume to n\n");
  printf ("\t-reclev=n\tset recording level to n\n");
  printf ("\t-recmon=n\tset recording monitor level to n\n");
  printf ("\t-help\t\tshow this screen\n");
  printf ("\tuse volume=(left,right) for stereo setting.\n");
}

int 
main (int argc, char *argv[])
{
  int k;


  mixer_fd = open ("/dev/mixer", O_RDWR);
  if (mixer_fd < 0)
    {
      perror ("Can not open mixer device!");
      exit (-1);
    }

  if (ioctl (mixer_fd, SOUND_MIXER_READ_DEVMASK, &supported) < 0)
    supported = 0xffff;

  if (argc == 1)
    {
      check_all ("");
      printf ("\n`%s -help\' for more information.\n", argv[0]);
      close (mixer_fd);
      exit (0);
    }
  if (strcmp (HELP_OPT, argv[1]) == 0)
    {
      usage (argv[0]);
      close (mixer_fd);
      exit (0);
    }
  for (k = 1; k < argc; k++)
  {
//    printf("Mixer: checking arg: %s.\n",argv[k]);
    check_all (argv[k]);
  }
  close (mixer_fd);
  exit (0);
}

