/**
 * Copyright 1993 Network Computing Devices, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name Network Computing Devices, Inc. not be
 * used in advertising or publicity pertaining to distribution of this
 * software without specific, written prior permission.
 *
 * THIS SOFTWARE IS PROVIDED `AS-IS'.  NETWORK COMPUTING DEVICES, INC.,
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT
 * LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, OR NONINFRINGEMENT.  IN NO EVENT SHALL NETWORK
 * COMPUTING DEVICES, INC., BE LIABLE FOR ANY DAMAGES WHATSOEVER, INCLUDING
 * SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, INCLUDING LOSS OF USE, DATA,
 * OR PROFITS, EVEN IF ADVISED OF THE POSSIBILITY THEREOF, AND REGARDLESS OF
 * WHETHER IN AN ACTION IN CONTRACT, TORT OR NEGLIGENCE, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Author:	Greg Renda <greg@ncd.com>
 * 		Network Computing Devices, Inc.
 * 		350 North Bernardo Ave.
 * 		Mountain View, CA  94043
 *
 * $NCDId: @(#)aupanel.c,v 1.13 1994/11/01 23:20:06 greg Exp $
 */

#include <stdio.h>
#ifndef SYSV
#include <audio/Aos.h>		/* for string and other os stuff */
#endif
#include <audio/Afuncs.h> 	/* for bcopy et. al. */
#include <audio/audiolib.h>
#include <audio/soundlib.h>

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Cardinals.h>
#include <audio/Xtutil.h>

/* widgets */
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/MenuButton.h>
#include <X11/Xaw/SmeBSB.h>

#include <Slider.h>

#define	APP_CLASS		"Aupanel"

#define USAGE "\
usage: aupanel [-a audioserver]\
"

#define MakeCommandButton(w, parent, label, callback)			       \
{									       \
    (w) = XtCreateManagedWidget(label, commandWidgetClass, parent, NULL, 0);   \
									       \
    if ((void *) (callback) != NULL)					       \
	XtAddCallback(w, XtNcallback, callback, g);			       \
}

#define MakeLabel(w, parent, label)					       \
{									       \
    (w) = XtCreateManagedWidget(label, labelWidgetClass, parent, NULL, 0);     \
}

#define MakeWidget(w, parent, type, name)				       \
{									       \
    (w) = XtCreateManagedWidget(name, type, parent, NULL, 0);		       \
}

typedef struct
{
    Widget          top,
                    form,
                    quit,
                    query,
                    menu,
                    menuButton,
                    device,
                    gainSlider,
                    inputModeLabel,
                    inputModeLine,
                    inputModeMic,
                    outputModeLabel,
                    outputModeSpk,
                    outputModeHead,
                    outputModeLine;
    AuServer       *aud;
    int             numDevices,
                    deviceNum;
    AuDeviceAttributes *da;
}               GlobalDataRec, *GlobalDataPtr;

static String   defaultResources[] =
{
    "*input:                          true",
    "*font:                           *courier-medium-r-normal*140*",
    "*query.label:                    Query",
    "*devices.label:                  Devices",
    "*devices.fromHoriz:              query",
    "*quit.label:                     Quit",
    "*quit.fromHoriz:                 devices",
    "*deviceLabel.label:              Stereo Channel Output",
    "*deviceLabel.fromVert:           query",
    "*deviceLabel.font:               *courier-bold-r-normal*140*",
    "*deviceLabel.resizable:          true",
    "*deviceLabel.borderWidth:        0",
    "*gainSlider.label:               \\ Gain:  %3d%%",
    "*gainSlider.fromVert:            deviceLabel",
    "*gainSlider.min:                 0",
    "*gainSlider.max:                 100",
    "*gainSlider.resizable:           true",
    "*inputModeLabel.label:           \\ Input mode:",
    "*inputModeLabel.fromVert:        gainSlider",
    "*inputModeLabel.borderWidth:     0",
    "*inputModeLine.label:            Line-In",
    "*inputModeLine.fromHoriz:        inputModeLabel",
    "*inputModeLine.fromVert:         gainSlider",
    "*inputModeMic.label:             Microphone",
    "*inputModeMic.fromHoriz:         inputModeLine",
    "*inputModeMic.fromVert:          gainSlider",
    "*inputModeMic.radioGroup:        inputModeLine",
    "*outputModeLabel.label:          Output mode:",
    "*outputModeLabel.fromVert:       inputModeLabel",
    "*outputModeLabel.borderWidth:    0",
    "*outputModeSpk.label:            Speaker",
    "*outputModeSpk.fromHoriz:        outputModeLabel",
    "*outputModeSpk.fromVert:         inputModeLine",
    "*outputModeHead.label:           Headphone",
    "*outputModeHead.fromHoriz:       outputModeSpk",
    "*outputModeHead.fromVert:        inputModeLine",
    "*outputModeLine.label:           Line-Out",
    "*outputModeLine.fromHoriz:       outputModeHead",
    "*outputModeLine.fromVert:        inputModeLine",
    NULL
};

static void
fatalError(message, arg)
char           *message,
               *arg;
{
    fprintf(stderr, message, arg);
    fprintf(stderr, "\n");
    exit(1);
}

static void
quitCB(w, gp, call_data)
Widget          w;
XtPointer       gp;
XtPointer       call_data;
{
    exit(0);
}

static void
showDevice(g)
GlobalDataPtr   g;
{
    Boolean         inputModeEnable, outputModeEnable,
                    gainEnable;
    AuDeviceAttributes *da = &g->da[g->deviceNum];

    XtVaSetValues(g->device, XtNlabel, AuDeviceDescription(da)->data, NULL);

    gainEnable = AuDeviceChangableMask(da) & AuCompDeviceGainMask ?
	True : False;
    XtVaSetValues(g->gainSlider, XtNsensitive, gainEnable, NULL);

    if (gainEnable)
	XtVaSetValues(g->gainSlider, XtNvalue,
		      AuFixedPointRoundUp(AuDeviceGain(da)), NULL);

    inputModeEnable = (AuDeviceChangableMask(da) & AuCompDeviceInputModeMask) &&
	(AuDeviceKind(da) == AuComponentKindPhysicalInput) ? True : False;

    outputModeEnable =
	(AuDeviceChangableMask(da) & AuCompDeviceOutputModeMask) &&
	(AuDeviceKind(da) == AuComponentKindPhysicalOutput) ? True : False;

    XtVaSetValues(g->inputModeLabel, XtNsensitive, inputModeEnable, NULL);
    XtVaSetValues(g->inputModeLine, XtNsensitive, inputModeEnable, NULL);
    XtVaSetValues(g->inputModeMic, XtNsensitive, inputModeEnable, NULL);

    XtVaSetValues(g->outputModeLabel, XtNsensitive, outputModeEnable, NULL);
    XtVaSetValues(g->outputModeSpk, XtNsensitive, outputModeEnable, NULL);
    XtVaSetValues(g->outputModeHead, XtNsensitive, outputModeEnable, NULL);
    XtVaSetValues(g->outputModeLine, XtNsensitive, outputModeEnable, NULL);

    if (inputModeEnable)
    {
	Boolean mic = AuDeviceInputMode(da) == AuDeviceInputModeMicrophone;

	XtCallActionProc(g->inputModeLine, mic ? "reset" : "set",
			 NULL, NULL, 0);
	XtCallActionProc(g->inputModeMic, mic ? "set" : "reset",
			 NULL, NULL, 0);
    }
    else
    {
	XtCallActionProc(g->inputModeLine, "reset", NULL, NULL, 0);
	XtCallActionProc(g->inputModeMic, "reset", NULL, NULL, 0);
    }

    if (outputModeEnable)
    {
	XtCallActionProc(g->outputModeSpk,
			 AuDeviceOutputMode(da) & AuDeviceOutputModeSpeaker ?
			 "set" : "reset", NULL, NULL, 0);
	XtCallActionProc(g->outputModeHead,
			 AuDeviceOutputMode(da) & AuDeviceOutputModeHeadphone ?
			 "set" : "reset", NULL, NULL, 0);
	XtCallActionProc(g->outputModeLine,
			 AuDeviceOutputMode(da) & AuDeviceOutputModeLineOut ?
			 "set" : "reset", NULL, NULL, 0);
    }
    else
    {
	XtCallActionProc(g->outputModeSpk, "reset", NULL, NULL, 0);
	XtCallActionProc(g->outputModeHead, "reset", NULL, NULL, 0);
	XtCallActionProc(g->outputModeLine, "reset", NULL, NULL, 0);
    }

}

static void
queryCB(w, gp, call_data)
Widget          w;
XtPointer       gp;
XtPointer       call_data;
{
    GlobalDataPtr   g = (GlobalDataPtr) gp;

    AuFreeDeviceAttributes(g->aud, g->numDevices, g->da);
    g->da = AuListDevices(g->aud, 0, NULL, &g->numDevices, NULL);
    showDevice(g);
}

static void
inputModeCB(w, gp, call_data)
Widget          w;
XtPointer       gp;
XtPointer       call_data;
{
    GlobalDataPtr   g = (GlobalDataPtr) gp;
    AuDeviceAttributes *da = &g->da[g->deviceNum];
    Boolean state;

    XtVaGetValues(w, XtNstate, &state, NULL);

    if (!state)			/* ignore resets */
	return;

    AuDeviceInputMode(da) = (int) XawToggleGetCurrent(w) == 1
	? AuDeviceInputModeLineIn : AuDeviceInputModeMicrophone;

    AuSetDeviceAttributes(g->aud, AuDeviceIdentifier(da),
			  AuCompDeviceInputModeMask, da, NULL);
}

static void
outputModeCB(w, gp, call_data)
Widget          w;
XtPointer       gp;
XtPointer       call_data;
{
    GlobalDataPtr   g = (GlobalDataPtr) gp;
    AuDeviceAttributes *da = &g->da[g->deviceNum];
    Boolean state;
    int mode;

    mode = w == g->outputModeSpk ? AuDeviceOutputModeSpeaker :
	(w == g->outputModeHead ? AuDeviceOutputModeHeadphone :
	 AuDeviceOutputModeLineOut);

    XtVaGetValues(w, XtNstate, &state, NULL);

    if (state)
	AuDeviceOutputMode(da) |= mode;
    else
	AuDeviceOutputMode(da) &= ~mode;

    AuSetDeviceAttributes(g->aud, AuDeviceIdentifier(da),
			  AuCompDeviceOutputModeMask, da, NULL);
    queryCB(w, g, call_data);
}

static void
menuCB(w, gp, call_data)
Widget          w;
XtPointer       gp;
XtPointer       call_data;
{
    GlobalDataPtr   g = (GlobalDataPtr) gp;
    int             i;
    String          string;

    XtVaGetValues(w, XtNlabel, &string, NULL);
    XtVaSetValues(g->device, XtNlabel, string, NULL);

    for (i = 0; i < g->numDevices; i++)
	if (!strcmp(string, AuDeviceDescription(&g->da[i])->data))
	    break;

    g->deviceNum = i;
    queryCB(w, g, call_data);
    showDevice(g);
}

static void
setGain(w, gp, valuep)
Widget          w;
XtPointer       gp;
XtPointer       valuep;
{
    GlobalDataPtr   g = (GlobalDataPtr) gp;
    AuDeviceAttributes *da = &g->da[g->deviceNum];
    int             value = (int) valuep;

    AuDeviceGain(da) = AuFixedPointFromSum(value, 0);
    AuSetDeviceAttributes(g->aud, AuDeviceIdentifier(da),
			  AuCompDeviceGainMask, da, NULL);
}

static void
createWidgets(g)
GlobalDataPtr   g;
{
    int             i;
    Widget          w;

    MakeWidget(g->form, g->top, formWidgetClass, "form");

    MakeCommandButton(g->query, g->form, "query", queryCB);

    g->menu = XtCreatePopupShell("menu", simpleMenuWidgetClass, g->form,
				 NULL, 0);

    g->da = AuListDevices(g->aud, 0, NULL, &g->numDevices, NULL);

    if (!g->numDevices)
	fatalError("no devices");

    for (i = 0; i < g->numDevices; i++)
    {
	MakeWidget(w, g->menu, smeBSBObjectClass,
		   AuDeviceDescription(&g->da[i])->data);
	XtAddCallback(w, XtNcallback, menuCB, g);
    }

    MakeWidget(g->menuButton, g->form, menuButtonWidgetClass, "devices");

    MakeCommandButton(g->quit, g->form, "quit", quitCB);

    MakeLabel(g->device, g->form, "deviceLabel");

    MakeWidget(g->gainSlider, g->form, sliderWidgetClass, "gainSlider");
    XtAddCallback(g->gainSlider, XtNcallback, setGain, g);

    MakeLabel(g->inputModeLabel, g->form, "inputModeLabel");
    MakeWidget(g->inputModeLine, g->form, toggleWidgetClass, "inputModeLine");
    MakeWidget(g->inputModeMic, g->form, toggleWidgetClass, "inputModeMic");
    XtAddCallback(g->inputModeLine, XtNcallback, inputModeCB, g);
    XtAddCallback(g->inputModeMic, XtNcallback, inputModeCB, g);
    XtVaSetValues(g->inputModeLine, XtNradioData, 1, NULL);
    XtVaSetValues(g->inputModeMic, XtNradioData, 2, NULL);

    MakeLabel(g->outputModeLabel, g->form, "outputModeLabel");
    MakeWidget(g->outputModeSpk, g->form, toggleWidgetClass, "outputModeSpk");
    MakeWidget(g->outputModeHead, g->form, toggleWidgetClass, "outputModeHead");
    MakeWidget(g->outputModeLine, g->form, toggleWidgetClass, "outputModeLine");
    XtAddCallback(g->outputModeSpk, XtNcallback, outputModeCB, g);
    XtAddCallback(g->outputModeHead, XtNcallback, outputModeCB, g);
    XtAddCallback(g->outputModeLine, XtNcallback, outputModeCB, g);
}

static void
alignWidgets(g)
GlobalDataPtr   g;
{
    Dimension       w,
                    w1;
    Position        x,
                    x1;
    int             d;

    XtVaGetValues(g->outputModeLine, XtNwidth, &w, XtNx, &x, NULL);
    XtVaGetValues(g->gainSlider, XtNhorizDistance, &d, NULL);
    XtVaSetValues(g->gainSlider, XtNwidth, w + x - d, NULL);

    XtVaGetValues(g->device, XtNhorizDistance, &d, NULL);
    XtVaSetValues(g->device, XtNwidth, w + x - d, NULL);

    XtVaGetValues(g->quit, XtNx, &x1, XtNhorizDistance, &d, XtNwidth, &w1,
		  NULL);
    XtVaSetValues(g->quit, XtNhorizDistance, w - w1 - (x1 - d - x), NULL);

    XtVaSetValues(g->device, XtNresizable, False, NULL);
}

int
main(argc, argv)
int             argc;
char          **argv;
{
    GlobalDataRec   globals;
    GlobalDataPtr   g = &globals;
    XtAppContext    appContext;
    char           *audioServer = NULL;

    g->top = XtVaAppInitialize(&appContext, APP_CLASS, NULL, ZERO,
			       &argc, argv, defaultResources, NULL, 0);

    if (argc == 3)
	if (!strncmp(argv[1], "-a", 2))
	    audioServer = argv[2];
	else
	    fatalError(USAGE);
    else if (argc != 1)
	fatalError(USAGE);

    if (!(g->aud = AuOpenServer(audioServer, 0, NULL, 0, NULL, NULL)))
	fatalError("Can't connect to audio server");

    createWidgets(g);
    XtRealizeWidget(g->top);
    alignWidgets(g);

    AuXtAppAddAudioHandler(appContext, g->aud);

    g->deviceNum = 0;
    showDevice(g);

    XtAppMainLoop(appContext);
    return 0;
}
