/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#define CONFIGURE_C

#define UI_OK          -2
#define UI_CANCEL      -1
#define UI_SAVE         1
#define UI_EXIT         2
#define UI_ORDER        3
#define UI_DIAG         4
#define UI_SORTCARDS    5
#define UI_ADDCARDS     6
#define UI_DELCARDS     7
#define UI_DSPIN        8
#define UI_DSPOUT       9
#define UI_DSPDUPLEX    10

#define UI_UP           0x7ffffffd
#define UI_DOWN         0x7ffffffc

#ifdef linux
#include <linux/version.h>
#endif

#include "timestamp.h"

#define PREALLOC_SIZE (512*1024)

#undef _KERNEL
#include <sys/types.h>
#if defined(sun) && !defined(__hpux)
#include <sys/mkdev.h>
#endif
#include <stdio.h>
#include <stdlib.h>

#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <oss_config.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <time.h>
#include "ui.h"

oss_audioinfo audiodevs[MAX_PCM_DEV];
int num_audiodevs = 0;

static char *nspath = NULL;

static int do_support = 0;

oss_reroute_t redir_read = { 0 }, redir_write =

{
0}, redir_readwrite =

{
0};

int fd = -1;
int screen_lines = 25;
char osslibdir[255] = "/usr/lib/oss";
int do_full_osstest = 0;

struct cardinfo
{
  char name[256];
  char driver[256];
  int is_virtual;
};

struct cardinfo cards[64] = { 0 };
static int ncards = 0;

void
get_osslibdir (void)
{
  FILE *f;
  char tmp[256];

  if ((f = fopen ("/etc/oss.conf", "r")) == NULL)
    {
      perror ("/etc/oss.conf");
      exit (-1);
    }

  while (fgets (tmp, 255, f) != NULL)
    {
      int l = strlen (tmp);
      if (l > 0 && tmp[l - 1] == '\n')
	tmp[l - 1] = 0;

      if (strncmp (tmp, "OSSLIBDIR=", 10) == 0)
	{
	  strcpy (osslibdir, &tmp[10]);
	  fclose (f);
	  return;
	}
    }

  fclose (f);

  fprintf (stderr, "Error: OSSLIBDIR not set in /etc/oss.conf\n");
  exit (-1);
}

static void
get_reroute (int mode, oss_reroute_t * rd)
{
  rd->mode = mode;

  if (ioctl (fd, OSSCTL_GET_REROUTE, rd) == -1)
    {
      perror ("Get reroute");
      exit (-1);
    }

#if 0
  printf ("Reroute for mode=%x\n", mode);
  for (i = 0; i < rd->devlist.ndevs; i++)
    {
      printf ("    %2d: %2d=%s\n", i, rd->devlist.devices[i],
	      audiodevs[rd->devlist.devices[i]].name);
    }
#endif

}

static void
set_reroute (FILE * f, int mode, oss_reroute_t * rd)
{
  int i;
  rd->mode = mode;

  if (ioctl (fd, OSSCTL_SET_REROUTE, rd) == -1)
    {
      perror ("Set reroute");
      return;
    }

  for (i = 0; i < rd->devlist.ndevs; i++)
    {
      fprintf (f, "%d ", rd->devlist.devices[i]);
    }
  fprintf (f, "\n");
}

static void
load_sysinfo (void)
{
  oss_sysinfo sysinfo;
  oss_audioinfo *ainfo;
  oss_reroute_t rd;

  int i;


  if (ioctl (fd, OSS_SYSINFO, &sysinfo) == -1)
    {
      int err = errno;

      perror ("OSS_SYSINFO");
      if (err == EINVAL)
	fprintf (stderr, "Error: OSS version 3.9.7 or later is required\n");
      exit (-1);
    }

  num_audiodevs = sysinfo.numaudios;

  for (i = 0; i < num_audiodevs; i++)
    {
      ainfo = &audiodevs[i];
      ainfo->dev = i;

      if (ioctl (fd, SNDCTL_AUDIOINFO, ainfo) == -1)
	{
	  int err = errno;
	  perror ("SNDCTL_AUDIOINFO");

	  if (err = EINVAL)
	    fprintf (stderr, "Incompatible OSS version\n");
	  exit (-1);
	}

    }

  get_reroute (OPEN_READ, &redir_read);
  get_reroute (OPEN_WRITE, &redir_write);
  get_reroute (OPEN_READ | OPEN_WRITE, &redir_readwrite);

}


static void
save_changes (void)
{
  FILE *f;
  char tmp[1024];

  sprintf (tmp, "%s/etc/dspdevs.map", osslibdir);
  if ((f = fopen (tmp, "w")) == NULL)
    {
      perror (tmp);
      fprintf (stderr, "Saving config failed\n");
      return;
    }

  set_reroute (f, OPEN_READ, &redir_read);
  set_reroute (f, OPEN_WRITE, &redir_write);
  set_reroute (f, OPEN_READ | OPEN_WRITE, &redir_readwrite);

  fclose (f);
}

static void
append_file (ui_selection * sel, char *fname)
{

  FILE *f;
  char tmp[80];

  if ((f = fopen (fname, "r")) == NULL)
    ui_sel_addchoice (sel, "Failed to open the log file", UI_NONEID);
  else
    {

      while (fgets (tmp, sizeof (tmp) - 1, f) != NULL)
	{
	  char *p = tmp;

	  while (*p)
	    if (*p == '\n')
	      *p = 0;
	    else
	      p++;

	  ui_sel_addchoice (sel, tmp, UI_NONEID);
	}
      fclose (f);
    }
}

static void
append_open_file (ui_selection * sel, FILE * f)
{

  char tmp[80];

  while (fgets (tmp, sizeof (tmp) - 1, f) != NULL)
    {
      char *p = tmp;

      while (*p)
	if (*p == '\n')
	  *p = 0;
	else
	  p++;

      ui_sel_addchoice (sel, tmp, UI_NONEID);
    }
}

static void
do_reorder (oss_reroute_t * rec)
{
  int i, n = 0;
  int num;
  int act = 0;
  ui_selection *sel;
  char tmp[256];
  int redirect[MAX_PCM_DEV + 10];

  if (rec->devlist.ndevs == 0)
    {
      ui_message ("No devices to reorder", 1);
      return;
    }

  for (i = 0; i < rec->devlist.ndevs; i++)
    redirect[i] = rec->devlist.devices[i];

  do
    {
      sel = ui_sel_create ("Reorder devices", rec->devlist.ndevs + 1);

      ui_sel_addbutton (sel, " Move Up ", UI_UP);
      ui_sel_addbutton (sel, " Move Down ", UI_DOWN);
      ui_sel_addbutton (sel, " OK ", UI_OK);
      ui_sel_addbutton (sel, " Cancel ", -1);

      ui_sel_addhdr (sel,
		     "Select a device and move it up/down with the function buttons");
      ui_sel_addhdr (sel, "");

      n = 0;
      for (i = 0; i < rec->devlist.ndevs; i++)
	{
	  oss_audioinfo ai;
	  char *s = tmp;

	  ai.dev = redirect[i];
	  if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1)
	    strcpy (ai.name, "unknown");
	  s += sprintf (s, "/dev/dsp%d: %s", redirect[i], ai.name);

	  if (ai.max_channels > 2)
	    s += sprintf (s, " (MULTICH)");

	  if (ai.oformats & AFMT_AC3)
	    s += sprintf (s, " (AC3)");

	  ui_sel_addchoice (sel, tmp, i);
	  n++;
	}

      if (n == 0)
	{
	  ui_sel_delete (sel);
	  ui_message ("No devices to sort.", 1);
	  return;
	}

      ui_sel_setactive (sel, act);
      num = ui_sel_run (sel);
      ui_sel_delete (sel);
      if (num >= 0 && num <= rec->devlist.ndevs)
	act = num;

      if (num == UI_CANCEL)
	return;

      if (num == UI_UP)
	{
	  int tmp;

	  num = ui_sel_getactive (sel);
	  if (num == -1 || num == UI_NONEID)
	    continue;
	  if (num < 1)
	    continue;

	  tmp = redirect[num - 1];
	  redirect[num - 1] = redirect[num];
	  redirect[num] = tmp;
	  act = num - 1;
	}

      if (num == UI_DOWN)
	{
	  int tmp;
	  num = ui_sel_getactive (sel);
	  if (num == -1 || num == UI_NONEID)
	    continue;

	  if (num >= rec->devlist.ndevs - 1)
	    continue;
	  tmp = redirect[num + 1];
	  redirect[num + 1] = redirect[num];
	  redirect[num] = tmp;
	  act = num + 1;
	}

    }
  while (num != UI_OK);

  /* Save the new order */

  for (i = 0; i < rec->devlist.ndevs; i++)
    rec->devlist.devices[i] = redirect[i];
}


static void
do_diag (int full_test)
{
  char tmp[1024];
  ui_selection *sel;
  FILE *osstest;
  int ok = 0;

  ui_message ("Starting OSS diagnostics (osstest)", 0);
  if (full_test)
    sprintf (tmp, "/usr/bin/osstest -C -V 2>&1 | tee %s/logs/osstest.log",
	     osslibdir, osslibdir);
  else
    sprintf (tmp, "/usr/bin/osstest -C 2>&1 | tee %s/logs/osstest.log",
	     osslibdir, osslibdir);

  if ((osstest = popen (tmp, "r")) == NULL)
    {
      ui_message ("Failed to start the osstest utility", 1);
      return;
    }
  while (fgets (tmp, sizeof (tmp) - 1, osstest) != NULL)
    {
#if 0
      char *p = tmp;

      ok = 0;

      while (*p)
	{
	  if (p[0] == 'O' && p[1] == 'K')
	    {
	      ok = 1;
	      break;
	    }
	  p++;
	}

      p = tmp;
      while (*p)
	if (*p == '\n')
	  *p = 0;
	else
	  p++;
      if (*tmp == 0)
	continue;
#endif
      ui_message (tmp, 0);
    }

  if (pclose (osstest) == -1 || ok == 0)
    {
      ui_message ("One or more errors were detected during the self test", 1);
    }
  if (ok)
    ui_message ("All tests completed successfully.", 1);

  sel = ui_sel_create ("OSS Audio Self Test results", 1000);

  sprintf (tmp, "%s/docs/README.osstest", osslibdir);
  append_file (sel, tmp);

  ui_sel_addchoice (sel, "", UI_NONEID);
  ui_sel_addchoice (sel, "Self test log", UI_NONEID);
  ui_sel_addchoice (sel, "=============", UI_NONEID);

  sprintf (tmp, "%s/logs/osstest.log", osslibdir);
  append_file (sel, tmp);

  ui_sel_addbutton (sel, "  Ok  ", 1);

  ui_sel_run (sel);
  ui_sel_delete (sel);
}

static void
load_installed_drivers ()
{
  char tmp[256];
  FILE *f;
  char *p;
  int i;

  ncards = 0;
  if ((f = fopen ("/usr/lib/oss/etc/installed_drivers", "r")) == NULL)
    {
      perror ("/usr/lib/oss/etc/installed_drivers");
      return;
    }

  while (fgets (tmp, sizeof (tmp), f) != NULL)
    {
      p = tmp;

      /* first save the driver name */
      i = 0;
      while (*p && (*p != ' '))
	{
	  cards[ncards].driver[i++] = *p;
	  p++;
	}

      while (*p == ' ' || *p == '#')
	p++;

      /* now save the device description */
      i = 0;
      while (*p)
	{
	  if (*p == '\n')
	    *p = 0;
	  cards[ncards].name[i++] = *p;
	  p++;
	}

      /* Now flag virtual drivers */
      if (strncmp (cards[ncards].driver, "softoss", 7) == 0)
	cards[ncards].is_virtual = 1;
      if (strncmp (cards[ncards].driver, "imux", 4) == 0)
	cards[ncards].is_virtual = 1;
      if (strncmp (cards[ncards].driver, "osscore", 7) == 0)
	cards[ncards].is_virtual = 1;
      if (strncmp (cards[ncards].driver, "sadasupport", 7) == 0)
	cards[ncards].is_virtual = 1;

      ncards++;
    }

  fclose (f);
}

void
save_installed_drivers ()
{
  FILE *f;
  int i, num_virt;
  char tmp[256];


  /* open installed_drivers for writing */
  if ((f = fopen ("/usr/lib/oss/etc/installed_drivers", "w")) == NULL)
    {
      perror ("/usr/lib/oss/etc/installed_drivers");
      return;
    }

  /* write back installed_drivers */
  for (i = 0; i < ncards; i++)
    {
      if (cards[i].is_virtual != 1)
	{
	  sprintf (tmp, "%s #%s\n", cards[i].driver, cards[i].name);
	  fputs (tmp, f);
	}
    }

  /* write back virtual installed_drivers */
  for (i = 0; i < ncards; i++)
    {
      if (cards[i].is_virtual == 1)
	{
	  sprintf (tmp, "%s #%s\n", cards[i].driver, cards[i].name);
	  fputs (tmp, f);
	}
    }

  fclose (f);
}

static void
detect_cards (void)
{
  int i, j, num;
  ui_selection *sel;
  struct cardinfo orig_cardlist[64];
  int orig_ncards;

  if (ncards > 0)
    {
      orig_ncards = ncards;
      for (i = 0; i < orig_ncards; i++)
	orig_cardlist[i] = cards[i];
    }

  sel = ui_sel_create ("Auto Detect Sound Cards", ncards + 1);

  ui_sel_addbutton (sel, " OK", UI_OK);
  ui_sel_addbutton (sel, " Cancel ", -1);

  system ("/usr/sbin/ossdetect");

  load_installed_drivers ();


  if (ncards == 0)
    {
      ui_sel_delete (sel);
      ui_message ("No devices detected.", 1);
      return;
    }

  for (i = 0; i < ncards; i++)
    {
      ui_sel_addchoice (sel, cards[i].name, i);
    }

  num = ui_sel_run (sel);
  ui_sel_delete (sel);

  if (num == UI_OK)
    {
      return;
    }

  if (num == -1 || num == UI_NONEID)
    {
      /* restore the card list */
      ncards = orig_ncards;
      for (i = 0; i < ncards; i++)
	cards[i] = orig_cardlist[i];
      return;
    }
}



static void
delete_cards (void)
{
  char tmp[256];
  int i, j, num;
  ui_selection *sel;


  if (ncards == 0)
    {
      ui_message ("No soundcards to delete!\n", 1);
      return;
    }


  sel = ui_sel_create ("Select device to be removed", ncards + 1);

  ui_sel_addbutton (sel, " OK ", UI_OK);
  ui_sel_addbutton (sel, " Cancel ", -1);

  for (i = 0; i < ncards; i++)
    {
      ui_sel_addchoice (sel, cards[i].name, i);
    }

  num = ui_sel_run (sel);
  ui_sel_delete (sel);

  if (num == UI_OK)
    num = ui_sel_getactive (sel);

  if (num == -1 || num == UI_NONEID)
    return;

#if 0
  sprintf (tmp, "Deleted device: %s", cards[num].name);
  ui_message (tmp, 1);
#endif

#if defined (sun)
  sprintf (tmp, "rem_drv %s", cards[num].driver);
  system (tmp);
#endif

  for (i = num; i < ncards; i++)
    cards[i] = cards[i + 1];
  ncards--;

}


static void
reorder_cards (void)
{
  int i, j, n, num;
  int act = 0;
  char tmp[256];
  int redirect[64];
  struct cardinfo new_cards[64];
  ui_selection *sel;


  if (ncards == 0)
    {
      ui_message ("No devices to reorder", 1);
      return;
    }

  /* copy the card list first */
  for (i = 0; i < ncards; i++)
    new_cards[i] = cards[i];

  for (i = 0; i < 64; i++)
    redirect[i] = i;

  do
    {
      sel = ui_sel_create ("Reorder devices", ncards + 1);

      ui_sel_addbutton (sel, " Move Up ", UI_UP);
      ui_sel_addbutton (sel, " Move Down ", UI_DOWN);
      ui_sel_addbutton (sel, " OK ", UI_OK);
      ui_sel_addbutton (sel, " Cancel ", -1);

      ui_sel_addhdr (sel,
		     "Select a device and move it up/down with the function buttons");
      ui_sel_addhdr (sel, "");
      ui_sel_addhdr (sel,
		     "Notice: Regardless of the device order the Virtual Mixer and Input Multiplexer");
      ui_sel_addhdr (sel,
		     "devices will be initialized in fixed order AFTER all PCI and USB cards.");
      for (i = 0; i < ncards; i++)
	{
	  if (new_cards[redirect[i]].is_virtual == 1)
	    continue;

	  ui_sel_addchoice (sel, new_cards[redirect[i]].name, i);
	}

      ui_sel_setactive (sel, act);
      num = ui_sel_run (sel);
      ui_sel_delete (sel);
      if (num >= 0 && num <= ncards)
	act = num;

      if (num == UI_CANCEL)
	return;

      if (num == UI_UP)
	{
	  int tmp;

	  num = ui_sel_getactive (sel);
	  if (num == -1 || num == UI_NONEID)
	    continue;
	  if (num < 1)
	    continue;

	  tmp = redirect[num - 1];
	  redirect[num - 1] = redirect[num];
	  redirect[num] = tmp;
	  act = num - 1;
	}

      if (num == UI_DOWN)
	{
	  int tmp;
	  num = ui_sel_getactive (sel);
	  if (num == -1 || num == UI_NONEID)
	    continue;

	  if (num >= ncards - 1)
	    continue;
	  tmp = redirect[num + 1];
	  redirect[num + 1] = redirect[num];
	  redirect[num] = tmp;
	  act = num + 1;
	}

    }
  while (num != UI_OK);

  /* now write back the new card list back to the cardlist */
  for (i = 0; i < ncards; i++)
    cards[i] = new_cards[redirect[i]];
}

void
show_devices (ui_selection * sel)
{
  int i, j;
  char tmp[256];

  ui_sel_delhdrs (sel);
  ui_sel_hdrdesc (sel, "Configured Devices");

  for (i = 0; i < ncards; i++)
    {
      ui_sel_addhdr (sel, cards[i].name);
    }
}

static int
ossctl (void)
{
  int cmd;
  struct stat st;
  char cmdline[1024];

  ui_selection *sel;

  sel = ui_sel_create ("Open Sound System - Control menu", 25);
  show_devices (sel);
  ui_sel_addchoice (sel, "Save changes and Exit", UI_SAVE);
  ui_sel_addchoice (sel, "Cancel changes and Exit", UI_EXIT);

  ui_sel_addchoice (sel, "Detect Sound Cards", UI_ADDCARDS);
  ui_sel_addchoice (sel, "Remove Sound Card", UI_DELCARDS);
  ui_sel_addchoice (sel, "Reorder Sound Card", UI_SORTCARDS);
  ui_sel_addchoice (sel, "Audio self test", UI_DIAG);

  ui_sel_addchoice (sel, "/dev/dsp input device assignment", UI_DSPIN);
  ui_sel_addchoice (sel, "/dev/dsp output device assignment", UI_DSPOUT);
  ui_sel_addchoice (sel, "/dev/dsp duplex device assignment", UI_DSPDUPLEX);

  ui_sel_addbutton (sel, "Ok", UI_OK);
  ui_sel_addbutton (sel, "Save and exit", UI_SAVE);
  ui_sel_addbutton (sel, "Exit without saving", UI_EXIT);

  while ((cmd = ui_sel_run (sel)))
    {
      if (cmd == UI_OK)
	cmd = ui_sel_getid (sel);
      ui_sel_setactive (sel, 0);

      switch (cmd)
	{
	case UI_EXIT:
#ifndef SIMPLE_CONFIG
	  ui_message ("Exit without saving changes", 0);
#endif
	  return 0;
	  break;

	case UI_SAVE:
	  ui_message ("Saving changes", 0);
	  save_changes ();
	  save_installed_drivers ();
	  return 1;
	  break;

	case UI_DIAG:
	  do_diag (do_full_osstest);
	  show_devices (sel);
	  break;

	case UI_SORTCARDS:
	  reorder_cards ();
	  show_devices (sel);
	  break;

	case UI_ADDCARDS:
	  detect_cards ();
	  show_devices (sel);
	  break;

	case UI_DELCARDS:
	  delete_cards ();
	  show_devices (sel);
	  break;

	case UI_DSPIN:
	  do_reorder (&redir_read);
	  break;

	case UI_DSPOUT:
	  do_reorder (&redir_write);
	  break;

	case UI_DSPDUPLEX:
	  do_reorder (&redir_readwrite);
	  break;

	case UI_NONEID:
	  break;
	}
    }

  fprintf (stderr, "Aborting\n");
  ui_sel_delete (sel);
  return 0;
}

int
main (int argc, char *argv[])
{
  int i, j, n;

  char *p, *s;
  char tmp[256], *path;

  for (j = 1; j < argc; j++)
    for (i = 0; i < strlen (argv[1]); i++)
      switch (argv[j][i])
	{
	case 'V':
	  do_full_osstest = 1;
	  break;

	case '-':
	  break;

	default:
	  fprintf (stderr, "Warning: Unrecognized option %c\n", argv[1][i]);
	}

  if ((fd = open ("/dev/mixer0", O_RDWR, 0)) == -1 && !do_support)
    {
      int err = errno;

      perror ("/dev/mixer0");
      errno = err;
      exit (-1);
    }

  get_osslibdir ();

  if ((p = getenv ("LINES")) != NULL)
    {
      screen_lines = atoi (p);
      if (screen_lines < 16 || screen_lines > 150)
	screen_lines = 25;
    }

  if (ui_init (osslibdir) != 0)
    exit (-1);

  load_sysinfo ();
  load_installed_drivers ();

  ossctl ();

  ui_close ();

  exit (0);
}
