/* ***************************************************************************+
 * ITX package (cnrg.itx) for telephony application programming.              *
 * Copyright (c) 1999  Cornell University, Ithaca NY.                         *
 * A copy of the license is distributed with this package.  Look in the docs  *
 * directory, filename GPL.  Contact information: bergmark@cs.cornell.edu     *
 ******************************************************************************/

#include <windows.h>
#include <mmsystem.h>
#include <MMREG.H>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

__declspec(dllexport) DWORD __cdecl RNIGetCompatibleVersion();
#include <native.h>  // RNI declarations (part of Microsoft SDK for Java)
__declspec(dllexport) DWORD __cdecl RNIGetCompatibleVersion() { return RNIVER; }

#include "cnrg_itx_datax_jaudio_JAudioInfo.h"
#include "cnrg_itx_datax_jaudio_JAudioDevice.h"
#include "cnrg_itx_datax_jaudio_JAudioIn.h"
#include "cnrg_itx_datax_jaudio_JAudioOut.h"
#include "cnrg_itx_datax_jaudio_JAudioBlock.h"
#include "cnrg_itx_datax_jaudio_JAudioMessage.h"
#include "cnrg_itx_datax_jaudio_JAudioMessageThread.h"

#define dprintf gDebug && printf
#define CHECK_ERR(dev, err, msg) ((dev)->lastError = (err) ? (formatError((dev),(err),(msg)), 1): 0)

int gDebug = 1;

void formatError(struct Hcnrg_itx_datax_jaudio_JAudioDevice *pThis, int err, char *msg);

// Custom Windows message indicating end of MessageThread
#define WM_STOP_JAUDIO (WM_USER + 1)

// JAudioInfo.java
__declspec(dllexport)
long cnrg_itx_datax_jaudio_JAudioInfo_numDevicesN (struct Hcnrg_itx_datax_jaudio_JAudioInfo *pThis)
{
	int nIN = waveInGetNumDevs();
	int nOUT = waveOutGetNumDevs();

	dprintf("JAudioDeviceN: %d input and %d output devices detected\n", nIN, nOUT);
	return (nIN < nOUT)?nIN:nOUT;
}

// JAudioDevice.java
__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioDevice_initN(struct Hcnrg_itx_datax_jaudio_JAudioDevice *pThis, 
											  long msgThreadID, 
											  long debug)
{
	HWAVEOUT hwo;
	HWAVEIN hwi;
	PCMWAVEFORMAT wfx;
	int err;

	gDebug = debug;

	wfx.wf.wFormatTag = WAVE_FORMAT_PCM;
	wfx.wf.nChannels = 1;
	wfx.wf.nSamplesPerSec = cnrg_itx_datax_jaudio_JAudioDevice_SAMPLE_RATE;
	wfx.wf.nAvgBytesPerSec = cnrg_itx_datax_jaudio_JAudioDevice_SAMPLE_RATE;
	wfx.wf.nBlockAlign = 1;
	wfx.wBitsPerSample = cnrg_itx_datax_jaudio_JAudioDevice_SAMPLE_BITS;

	if ((pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_DUPLEX) || 
		(pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_OUTPUT))
	{
		dprintf("JAudioDeviceN: Opening device %d output\n", pThis->deviceID);
		err = waveOutOpen(&hwo, pThis->deviceID,(WAVEFORMATEX *)&wfx,(long)msgThreadID,0,
			CALLBACK_THREAD );
		if (CHECK_ERR(pThis,err,"Opening Device Output")) return;
		err = waveOutSetVolume(hwo, 0xFFFFFFFF);
		if (CHECK_ERR(pThis,err,"Setting Output Volume")) return;
		pThis->hwo = (long)hwo;
	}

	if ((pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_DUPLEX) || 
		(pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_INPUT))
	{
		dprintf("JAudioDeviceN: Opening device %d input\n", pThis->deviceID);
		err = waveInOpen(&hwi, pThis->deviceID,(WAVEFORMATEX *)&wfx,(long)msgThreadID,0,
			CALLBACK_THREAD );
		if (CHECK_ERR(pThis,err,"Opening Device Input")) {
			waveOutClose(hwo);
			return;
		}
		pThis->hwi = (long)hwi;
	}
}

__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioDevice_closeN(struct Hcnrg_itx_datax_jaudio_JAudioDevice *pThis)
{
	int err1, err2, err3, err4;

	if ((pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_DUPLEX) || 
		(pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_OUTPUT))
	{
		dprintf("JAudioDeviceN: Closing device out = %d\n", (HWAVEOUT)pThis->hwo);
		err1 = waveOutReset((HWAVEOUT)pThis->hwo);
		err2 = waveOutClose((HWAVEOUT)pThis->hwo);
	}
	if ((pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_DUPLEX) || 
		(pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_INPUT))
	{
		dprintf("JAudioDeviceN: Closing device in = %d\n", (HWAVEOUT)pThis->hwi);
		err3 = waveInReset((HWAVEIN)pThis->hwi);
		err4 = waveInClose((HWAVEIN)pThis->hwi);
	}

	if ((pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_DUPLEX) || 
		(pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_OUTPUT))
	{
		if (CHECK_ERR(pThis,err1,"Closing Device Output")) return;
		if (CHECK_ERR(pThis,err2,"Closing Device Output")) return;
	}
	if ((pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_DUPLEX) || 
		(pThis->mode == cnrg_itx_datax_jaudio_JAudioDevice_INPUT))
	{
		if (CHECK_ERR(pThis,err3,"Closing Device Input")) return;
		if (CHECK_ERR(pThis, err4, "Closing Device Input")) return;
	}
}

// JAudioMessageThread.java
__declspec(dllexport)
long cnrg_itx_datax_jaudio_JAudioMessageThread_getThreadIDN(struct Hcnrg_itx_datax_jaudio_JAudioMessageThread *pThis)
{
	return GetCurrentThreadId();
}

__declspec(dllexport)
long cnrg_itx_datax_jaudio_JAudioMessageThread_getMessageN(struct Hcnrg_itx_datax_jaudio_JAudioMessageThread *pThis, 
											struct Hcnrg_itx_datax_jaudio_JAudioMessage *pMsg)
{
	int done;
	MSG msg;
	UINT min = WOM_OPEN, max = WIM_DATA;	// these bound the messages that we want
	int status;
	// we will be down here a long time (nearly all the time) so we want to enable GC
	struct { Hcnrg_itx_datax_jaudio_JAudioMessage *safe; } gcsafe;
	GCFrame gcf;
	GCFramePush(&gcf, &gcsafe, sizeof(gcsafe));
	gcsafe.safe = pMsg;
	GCEnable();

	// NEW 4/21/1999
	done = 0;

	while (done != 1)
	{
		status = GetMessage(&msg, (HWND)NULL, 0, 0);

		if (status <= 0)
		{
			done = 1;
		}
		else
		{
			// I'm not sure if this is necessary, but it shouldn't hurt...
			TranslateMessage(&msg);
			DispatchMessage(&msg);

			// Continue to get messages until we get one of interest
			if (((msg.message >= min) && (msg.message <= max)) || (msg.message == WM_STOP_JAUDIO))
			{
				done = 1;
			}
		}
	}
	// NEW 4/21/1999

	// Continue to get messages until we get one of interest
	//status = GetMessage(&msg, (HWND)NULL, min, max);

	GCDisable();
	GCFramePop(&gcf);

	pMsg = gcsafe.safe;

	// If we get a WM_QUIT or an error occurs, stop JAudio...
	if (status <= 0) 
	{
		msg.message = WM_STOP_JAUDIO;
	}
	pMsg->message = msg.message;
	pMsg->param1 = msg.wParam;
	pMsg->param2 = msg.lParam;

	if (msg.message == WOM_DONE) {
		PWAVEHDR pwh = (PWAVEHDR) msg.lParam;
		if (waveOutUnprepareHeader ((HWAVEOUT)pwh->dwUser, pwh, sizeof (WAVEHDR)) != MMSYSERR_NOERROR)
			dprintf ("Error in unpreparing the waveheader\n");
	}
	else if (msg.message == WIM_DATA) {
		PWAVEHDR pwh = (PWAVEHDR) msg.lParam;
		if (waveInUnprepareHeader ((HWAVEIN)pwh->dwUser, pwh, sizeof (WAVEHDR)) != MMSYSERR_NOERROR)
			dprintf ("Error in unpreparing the waveheader\n");
	}

	return status;
}

__declspec(dllexport) 
long cnrg_itx_datax_jaudio_JAudioMessageThread_postThreadMessageN(struct Hcnrg_itx_datax_jaudio_JAudioMessageThread *pThis, 
																  long threadID, 
																  struct Hcnrg_itx_datax_jaudio_JAudioMessage *pMsg)
{
	return PostThreadMessage(threadID, pMsg->message, pMsg->param1, pMsg->param2);
}

/// AudioOut.java
__declspec(dllexport)

void cnrg_itx_datax_jaudio_JAudioOut_startN(struct Hcnrg_itx_datax_jaudio_JAudioOut *pThis)
{
	int err = waveOutRestart((HWAVEOUT)pThis->device->hwo);
	CHECK_ERR(pThis->device,err, "Starting Output");
}

__declspec(dllexport)
void __cdecl cnrg_itx_datax_jaudio_JAudioOut_stopN(struct Hcnrg_itx_datax_jaudio_JAudioOut *pThis)
{
	int err = waveOutPause((HWAVEOUT)pThis->device->hwo);
	CHECK_ERR(pThis->device,err, "Stopping Output");
}

__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioOut_putBlockN(struct Hcnrg_itx_datax_jaudio_JAudioOut *pThis, 
											   struct Hcnrg_itx_datax_jaudio_JAudioBlock *pBlock)
{
	int err;
	HWAVEOUT hwo = (HWAVEOUT)pThis->device->hwo;
	int dlen = pBlock->data->length;
	char *data = (char *)malloc(dlen);
	int whs = sizeof(WAVEHDR);
	PWAVEHDR pwh = (PWAVEHDR)malloc(whs);

	memcpy(data, pBlock->data->body, dlen); // we NEED a copy because of gc... no way to pin memory

	pBlock->wpHeader = (int)pwh;
	pBlock->wpData = (int)data;

	pwh->lpData = data;
	pwh->dwBufferLength = dlen;
	pwh->dwLoops = 0;
	pwh->dwFlags = 0;
	pwh->dwUser = (DWORD)hwo;

    dprintf("JAudioDeviceN: Buffer length %d for output\n", pwh->dwBufferLength);

	err = waveOutPrepareHeader(hwo,pwh,whs);
	if (CHECK_ERR(pThis->device,err,"Preparing Output Header")) {
		cnrg_itx_datax_jaudio_JAudioBlock_freeN(pBlock);
		return;
	}

	err = waveOutWrite(hwo, pwh, whs);
	if (CHECK_ERR(pThis->device,err,"Sending Output Buffer")) {
		cnrg_itx_datax_jaudio_JAudioBlock_freeN(pBlock);
		return;
	}
}

// AudioIn.java

__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioIn_startN(struct Hcnrg_itx_datax_jaudio_JAudioIn *pThis)
{
	int err = waveInStart((HWAVEIN)pThis->device->hwi);
	CHECK_ERR(pThis->device,err, "Starting Input");
}
__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioIn_stopN(struct Hcnrg_itx_datax_jaudio_JAudioIn *pThis)
{
	int err = waveInStop((HWAVEIN)pThis->device->hwi);
	CHECK_ERR(pThis->device,err, "Stopping Input");
}

__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioIn_addBlockN(struct Hcnrg_itx_datax_jaudio_JAudioIn *pThis, 
											  struct Hcnrg_itx_datax_jaudio_JAudioBlock *pBlock)
{
	int err;
	HWAVEIN hwi = (HWAVEIN)pThis->device->hwi;
	int whs = sizeof(WAVEHDR);
	PWAVEHDR pwh = (PWAVEHDR)malloc(whs);
	int dlen = pBlock->desiredLength;
	char *data = (char *)malloc(dlen);

	pBlock->wpHeader = (int)pwh;
	pBlock->wpData = (int)data;

	pwh->lpData = data;
	pwh->dwBufferLength = dlen;
	pwh->dwLoops = 0;
	pwh->dwFlags = 0;
	pwh->dwUser = (DWORD)hwi;

	dprintf("JAudioDeviceN: Buffer length %d for input\n", pwh->dwBufferLength);

	err = waveInPrepareHeader(hwi,pwh,whs);
	if (CHECK_ERR(pThis->device,err,"Preparing Input Header")) {
		cnrg_itx_datax_jaudio_JAudioBlock_freeN(pBlock);
		return;
	}

	err = waveInAddBuffer(hwi, pwh, whs);
	if (CHECK_ERR(pThis->device,err,"Sending Input Buffer")) {
		cnrg_itx_datax_jaudio_JAudioBlock_freeN(pBlock);
		return;
	}
}


// AudioBlock.java
__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioBlock_freeN (struct Hcnrg_itx_datax_jaudio_JAudioBlock *pThis)
{
	if (pThis->wpHeader) free((char *)pThis->wpHeader);
	if (pThis->wpData) free((char *)pThis->wpData);
}
__declspec(dllexport)
void cnrg_itx_datax_jaudio_JAudioBlock_copyDataN(struct Hcnrg_itx_datax_jaudio_JAudioBlock *pThis)
{
	if (pThis->wpData && pThis->data) {
		memcpy(pThis->data->body, (char *)pThis->wpData, pThis->desiredLength);
	}
}

///// error interpreting
void formatError(struct Hcnrg_itx_datax_jaudio_JAudioDevice *pThis, int err, char *msg)
{
	char output[256];
	char *buf = output;
	*buf = '\0';

	switch(err) 
	{
	case WAVERR_BADFORMAT:
		sprintf(buf,"JAudioDeviceN: Error %s -> unsupported wave format\n", msg);
		break;
	case WAVERR_STILLPLAYING:
		sprintf(buf,"JAudioDeviceN: Error %s -> still something playing\n", msg);
		break;
	case WAVERR_UNPREPARED:
		sprintf(buf,"JAudioDeviceN: Error %s -> header not prepared\n", msg);
		break;
	case WAVERR_SYNC:
		sprintf(buf,"JAudioDeviceN: Error %s -> device is synchronous\n", msg);
		break;
	case MMSYSERR_ERROR:
		sprintf(buf,"JAudioDeviceN: Error %s -> unspecified error\n", msg);
		break;
	case MMSYSERR_BADDEVICEID:
		sprintf(buf,"JAudioDeviceN: Error %s -> device ID out of range\n", msg);
		break;
	case MMSYSERR_NOTENABLED:
		sprintf(buf,"JAudioDeviceN: Error %s -> driver failed enable\n", msg);
		break;
	case MMSYSERR_ALLOCATED:
		sprintf(buf,"JAudioDeviceN: Error %s -> device already allocated\n", msg);
		break;
	case MMSYSERR_INVALHANDLE:
		sprintf(buf,"JAudioDeviceN: Error %s -> device handle is invalid\n", msg);
		break;
	case MMSYSERR_NODRIVER:
		sprintf(buf,"JAudioDeviceN: Error %s -> no device driver present\n", msg);
		break;
	case MMSYSERR_NOMEM:
		sprintf(buf,"JAudioDeviceN: Error %s -> memory allocation error\n", msg);
		break;
	case MMSYSERR_NOTSUPPORTED:
		sprintf(buf,"JAudioDeviceN: Error %s -> function isn't supported\n", msg);
		break;
	case MMSYSERR_BADERRNUM:
		sprintf(buf,"JAudioDeviceN: Error %s -> error value out of range\n", msg);
		break;
	case MMSYSERR_INVALFLAG:
		sprintf(buf,"JAudioDeviceN: Error %s -> invalid flag passed\n", msg);
		break;
	case MMSYSERR_INVALPARAM:
		sprintf(buf,"JAudioDeviceN: Error %s -> invalid parameter passed\n", msg);
		break;
	case MMSYSERR_HANDLEBUSY:
		sprintf(buf,"JAudioDeviceN: Error %s -> handle being used\n", msg);
		break;
	case MMSYSERR_INVALIDALIAS:
		sprintf(buf,"JAudioDeviceN: Error %s -> specified alias not found\n", msg);
		break;
	case MMSYSERR_BADDB:
		sprintf(buf,"JAudioDeviceN: Error %s -> bad registry database\n", msg);
		break;
	case MMSYSERR_KEYNOTFOUND:
		sprintf(buf,"JAudioDeviceN: Error %s -> registry key not found\n", msg);
		break;
	case MMSYSERR_READERROR:
		sprintf(buf,"JAudioDeviceN: Error %s -> registry read error\n", msg);
		break;
	case MMSYSERR_WRITEERROR:
		sprintf(buf,"JAudioDeviceN: Error %s -> registry write error\n", msg);
		break;
	case MMSYSERR_DELETEERROR:
		sprintf(buf,"JAudioDeviceN: Error %s -> registry delete error\n", msg);
		break;
	case MMSYSERR_VALNOTFOUND:
		sprintf(buf,"JAudioDeviceN: Error %s -> registry value not found\n", msg);
		break;
	case MMSYSERR_NODRIVERCB:
		sprintf(buf,"JAudioDeviceN: Error %s -> driver does not call DriverCallback\n", msg);
		break;
	case MMSYSERR_NOERROR:
		return;
	default:
		sprintf(buf,"JAudioDeviceN: Error %s -> unknown status %d(0x%x)\n", msg,err,err);
		break;
	}

	if (*buf != '\0')
	{
		dprintf("%s", buf);
	}

	Field_SetObject((HObject *)pThis,
					Class_GetField(Object_GetClass((HObject *)pThis),"lastErrorMsg"), 
					(HObject *)makeJavaString(buf, strlen(buf)));
}
