
#include <iostream>

#include "MonitorAudioWin32.h"
#include "math.h"
#include "../convert.h"
#include "../MonitorExceptions.h"
#include "../MonitorLogging.h"

#define usleep Sleep
#define short2float (1.0/32768.0)

using namespace std ;

MonitorAudioWin32::MonitorAudioWin32(const std::string* name, tSamplerate rate)
: MonitorAudio(name, rate) {
	run = false;
	hwi=NULL ;
	m_bBufferInUse=false ;
}

MonitorAudioWin32::~MonitorAudioWin32() {

}

bool MonitorAudioWin32::Start(void *format) {
	JThread::Start();
	return true;
}

void MonitorAudioWin32::Stop() {
	run = false;
	usleep(2000) ;
	CloseDevice();
}

int MonitorAudioWin32::InitDevice() {
	if ((pcm_name.length() == 0) || (pcm_rate == 0)) {
		LOG_ERROR("[WINMM] InitDevice Argument Error: pcm_name=" << pcm_name << ", pcm_rate=" << pcm_rate)
		return -1;
	}

	/* FIXME es wird einfach die erste Soundkarte geoeffnet */

	WAVEINCAPS   wic;
	WAVEFORMATEX wfx;
	UINT         nDevId;
	MMRESULT     rc;
	//UINT         nMaxDevices = waveInGetNumDevs();

	hwi = NULL;

	handle = CreateEvent(NULL,FALSE,FALSE,NULL);
	if (handle == NULL) {
		return -1;
	}
	std::string devString="/dev/dsp" ;
	std::string deviceName = pcm_name;

	nDevId = 0;
	if (deviceName.substr(0,devString.size())==devString)
	{
		nDevId=convertToInt(deviceName.substr(devString.size()));
		LOG_INFO("using windows device #" << nDevId  )
	}
	if ((nDevId<0))
	{
		nDevId=0 ;
	}


	rc = waveInGetDevCaps(nDevId, &wic, sizeof(wic));

	if (rc == MMSYSERR_NOERROR) {
		LOG_INFO("starting wavein for sounddevice: \"" << wic.szPname << "\"" )
		wfx.nChannels      = 2;      // stereo
		wfx.nSamplesPerSec = 22050;  // 22.05 kHz (22.05 * 1000)
		wfx.wFormatTag      = WAVE_FORMAT_PCM;
		wfx.wBitsPerSample  = 16;
		wfx.nBlockAlign     = wfx.nChannels * wfx.wBitsPerSample / 8;
		wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
		wfx.cbSize          = 0;

		//	rc = waveInOpen(&hwi, nDevId, &wfx, (DWORD)handle, 0, CALLBACK_EVENT | WAVE_FORMAT_DIRECT);
		rc = waveInOpen(&hwi, nDevId, &wfx, (DWORD_PTR)MonitorAudioWin32::waveInProc, (DWORD_PTR) this, CALLBACK_FUNCTION ); // | WAVE_FORMAT_DIRECT
		if (rc == MMSYSERR_NOERROR) {
			// break;
		} else {
			waveInErrorMsg (rc, "waveInOpen");
			return -1;
		}
	} else {
		waveInErrorMsg (rc, "waveInGetDevCaps");
		return -1 ;
	}


	// device not found, error condition
	//..................................

	if (hwi == NULL) {
		return -1;
	}

	// allocate two WAVEHDR buffer blocks
	//...................................
	for (int i = 0; i < MAX_BUFFERS; i++) {
		whin[i] = new WAVEHDR;
		if (whin[i]) {
			whin[i]->lpData = new char[DATABLOCK_SIZE];
			whin[i]->dwBufferLength = DATABLOCK_SIZE;
			whin[i]->dwFlags = 0;
		}
	}
	next_buffer = 0;

	return 1;
}

int MonitorAudioWin32::CloseDevice() {
	LOG_INFO("Closing sound device..."  )
	int i=0 ;
	// waveInReset Stops the device and sets all the pending buffers to zero.
	if (hwi != NULL) {

		waveInStop(hwi) ;
		// waveInReset friert den laufenden prozeß ein. Deswegen waveInStop
		//waveInReset(hwi);

		// Unprepare headers
		//..................

		for (i = 0; i < MAX_BUFFERS; i++) {
			waveInUnprepareHeader(hwi, whin[i], sizeof(WAVEHDR));
		}

		waveInClose(hwi);

		// I do not know if cleanup is really necessary since the buffers were dynamically allocated.
		// I think the memory is freed once the process is destroyed.

		// free the WAVEHDR buffer blocks
		//...............................

		for (i = 0; i < MAX_BUFFERS; i++) {
			if (whin[i] != NULL) {
				delete whin[i]->lpData;
				delete whin[i];
				whin[i] = NULL;
			}
		}
	}
	LOG_INFO("sound device closed"  )
	return 0;
}


void MonitorAudioWin32::waveInProc(HWAVEIN hwin, UINT uMsg, DWORD_PTR dwInstance,  DWORD_PTR dwParam1, DWORD_PTR dwParam2 )
{
	MonitorAudioWin32* me=(MonitorAudioWin32*) dwInstance ;
	WAVEHDR* pwhin= (WAVEHDR*) dwParam1;
	MMRESULT rc;

	switch (uMsg)
	{
	case WIM_DATA:
		rc=waveInUnprepareHeader(hwin,pwhin,sizeof(WAVEHDR));
		me->waveInErrorMsg (rc, "waveInUnprepareHeader");


    	/*
    	 while ((me->m_bBufferInUse)==true)
    	{
    		printf("Win32AudioThread waiting\n") ;
    		Sleep(10) ;
    	}
    	*/

    	me->m_bBufferInUse=true ;

		/* convert buffer and send off to pipe */
		me->audio_buffer->Samples = pwhin->dwBytesRecorded / sizeof (short) / 2;
		short *temp_buffer = (short *) pwhin->lpData;

		for (unsigned int i=0; i< (me->audio_buffer->Samples); i++) {
			me->audio_buffer->Left[i]  = temp_buffer[i*2]   * short2float;
			me->audio_buffer->Right[i] = temp_buffer[i*2+1] * short2float;
		}
		SetEvent(me->handle) ;
		/** Hauptprozess mitteilen, daß er einen Datenblock verarbeiten kann
		 * wir verlassen aber die Callback Funktion trotzdem schonmal
		 * FIXME: Koennte hier eine Race-Condition auftreten ?
		 */

		/* recycle buffer */
		rc = waveInPrepareHeader(hwin, pwhin, sizeof(WAVEHDR));
		if (rc == MMSYSERR_NOERROR) {
			rc = waveInAddBuffer(hwin, pwhin, sizeof(WAVEHDR));
			me->waveInErrorMsg (rc, "waveInAddBuffer");
		} else {
			me->waveInErrorMsg (rc, "waveInPrepareHeader");
		}
    }
}

void* MonitorAudioWin32::Thread() {
	int rc;

	rc=InitDevice() ;
	if ( rc < 0) {
		LOG_ERROR("[WINMM] Error initializing PCM device " << pcm_name)

		return NULL ;
	}

	/* prepare buffers */
	for (int i = 0; i < MAX_BUFFERS; i++) {
		rc = waveInPrepareHeader(hwi, whin[i], sizeof(WAVEHDR));

		// add buffers to the input queue
		if (rc == MMSYSERR_NOERROR) {
			rc = waveInAddBuffer(hwi, whin[i], sizeof(WAVEHDR));
			waveInErrorMsg (rc, "waveInAddBuffer");
		} else {
			waveInErrorMsg (rc, "waveInPrepareHeader");
		}
	}

	/* start waveIn */
	rc = waveInStart(hwi);
	waveInErrorMsg (rc, "waveInStart");

	/* check for ready buffers on every event */

	JThread::ThreadStarted();
	run = true;

	while(run) {
		int res = WaitForSingleObject(handle,1000);
		if (res==WAIT_OBJECT_0) {
			/**
			 * Wenn die Callbackfunktion den audio_buffer gefuellt hat
			 * schickt sie ein Event.
			 * Erst dann landen wir hier und verarbeiten dann auch den
			 * Audio_buffer
			 * Sinn dahinter ist, die Callbackfunktion schnell wieder zu
			 * verlassen
			 */
			ResetEvent(handle) ;
			DataFromSoundIn(audio_buffer, m_pOwner);
			m_bBufferInUse=false ;
		}
	}

	LOG_INFO("stopping soundcard" )
	/* stop waveIn */
	waveInStop(hwi);
	Sleep(1000) ;
	LOG_INFO("soundcard stopped"  )

	return NULL;
}

void MonitorAudioWin32::waveInErrorMsg(MMRESULT result, std::string addstr)
{
	if (result != MMSYSERR_NOERROR) {
		char errorbuffer[100];
		waveInGetErrorText (result, errorbuffer, 100);
		LOG_ERROR("[WINMM] Error " << result << ":" << errorbuffer << " (" << addstr << ")" )
	}
}

// vim: sw=4 ts=4 cindent
