/*  $Id: soundoutput_directsound.cpp,v 1.13 2004/04/12 15:38:48 sphair Exp $
**
**  ClanLib Game SDK
**  Copyright (C) 2003  The ClanLib Team
**  For a total list of contributers see the file CREDITS.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**  Lesser General Public License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
**
*/

#include "Sound/precomp.h"
#include "soundoutput_directsound.h"
#include "API/Core/System/error.h"
#include "API/Core/System/cl_assert.h"
#include "API/Core/System/log.h"
#include "Core/System/Win32/init_win32.h"

/////////////////////////////////////////////////////////////////////////////
// CL_SoundOutput_DirectSound construction:

CL_SoundOutput_DirectSound::CL_SoundOutput_DirectSound(int mixing_frequency) :
	CL_SoundOutput_Generic(mixing_frequency),
	directsound(0), soundbuffer(0),
	frag_size(0), buffer_size(0), bytes_per_sample(0),
	sleep_event(0), notify(0), has_sound(true)
{
	HRESULT err;
	err = DirectSoundCreate(NULL, &directsound, NULL);
	
	if (FAILED(err))
	{
		// throw CL_Error("Cannot open sound device.");
		frag_size = mixing_frequency/2;
		has_sound = false;
		return;
	}

	// We need a hwnd for our directsound interface:
	HWND hwnd = NULL;
//	if (CL_Display::cards.size() > 0) hwnd = ((CL_DisplayCard_Win32Compatible *) CL_Display::cards[0])->get_hwnd();
	if (hwnd == NULL)
	{
		WNDCLASS wndclass;

		wndclass.style = 0;
		wndclass.lpfnWndProc = DefWindowProc;
		wndclass.cbClsExtra = 0;
		wndclass.cbWndExtra = 0;
		wndclass.hInstance = CL_System_Win32::hInstance;
		wndclass.hIcon = NULL;
		wndclass.hCursor = LoadCursor (NULL,IDC_ARROW);
		wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
		wndclass.lpszMenuName = "ClanSound";
		wndclass.lpszClassName = "ClanSound";

		RegisterClass(&wndclass);

		hwnd = CreateWindow(
			"ClanSound",
			"ClanSound",
			0, // WS_POPUP,
			0,
			0,
			1,
			1,
			NULL,
			NULL,
			CL_System_Win32::hInstance,
			NULL);
	}
	cl_assert(hwnd != NULL);

	err = directsound->SetCooperativeLevel(hwnd, DSSCL_NORMAL);
	cl_assert(SUCCEEDED(err));


	// Create mixing buffer.
	WAVEFORMATEX format;
	format.wFormatTag = WAVE_FORMAT_PCM; 
	format.nChannels = 2;
	format.nSamplesPerSec = mixing_frequency;
	format.wBitsPerSample = 16;
	format.nBlockAlign = format.wBitsPerSample * format.nChannels / 8; 
	format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; 
	format.cbSize = 0;

	bytes_per_sample = format.nBlockAlign;

	frag_size = 2048;
	int num_fragments = 8;

	DSBUFFERDESC desc;
	desc.dwSize = sizeof(DSBUFFERDESC); 
	desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;

	desc.dwBufferBytes = frag_size * bytes_per_sample * num_fragments;
	desc.dwReserved = 0;
	desc.lpwfxFormat = &format; 

	err = directsound->CreateSoundBuffer(&desc, &soundbuffer, NULL);
	if (FAILED(err)) throw CL_Error("Cannot get primary sound buffer.");

	// Find size of buffer:

	DSBCAPS caps;
	memset(&caps, 0, sizeof(DSBCAPS));
	caps.dwSize = sizeof(DSBCAPS);
	
	err = soundbuffer->GetCaps(&caps);
	cl_assert(SUCCEEDED(err));

	buffer_size = caps.dwBufferBytes;
	cl_assert(buffer_size == frag_size * bytes_per_sample * num_fragments);
	
	// Clear buffer at beginning (good for debugging):
	DWORD size1, size2;
	void *ptr1;
	void *ptr2;
	err = soundbuffer->Lock(0, buffer_size, &ptr1, &size1, &ptr2, &size2, 0);
	cl_assert(SUCCEEDED(err));

	memset(ptr1, 0, size1);
	if (ptr2 != NULL) memset(ptr2, 0, size2);

	soundbuffer->Unlock(ptr1, size1, ptr2, size2);

	// Setup some sleeping :)
	sleep_event = CreateEvent(NULL, TRUE, FALSE, NULL);

	err = soundbuffer->QueryInterface(IID_IDirectSoundNotify, (void **) &notify);
//	cl_assert(SUCCEEDED(err));
	if (FAILED(err)) // directsoundnotify doesnt exist on nt4 with dx3. No sound avail.
	{
		// throw CL_Error("Cannot open sound device.");
		frag_size = mixing_frequency/2;
		has_sound = false;
		return;
	}

	DSBPOSITIONNOTIFY *notify_pos = new DSBPOSITIONNOTIFY[num_fragments];
	for (int i=0; i<num_fragments; i++)
	{
		notify_pos[i].dwOffset = i*frag_size*bytes_per_sample;
		notify_pos[i].hEventNotify = sleep_event;
	}
	notify->SetNotificationPositions(num_fragments, notify_pos);
	delete[] notify_pos;

	err = soundbuffer->Play(0, 0, DSBPLAY_LOOPING);
	cl_assert(SUCCEEDED(err));
}
	
CL_SoundOutput_DirectSound::~CL_SoundOutput_DirectSound()
{
	if (notify) notify->Release();
	if (soundbuffer) soundbuffer->Release();
	if (directsound) directsound->Release();
	if (sleep_event) CloseHandle(sleep_event);
}

/////////////////////////////////////////////////////////////////////////////
// CL_SoundOutput_DirectSound attributes:


/////////////////////////////////////////////////////////////////////////////
// CL_SoundOutput_DirectSound operations:

void CL_SoundOutput_DirectSound::silence()
{
}

int CL_SoundOutput_DirectSound::get_fragment_size()
{
	return frag_size;
}

void CL_SoundOutput_DirectSound::write_fragment(short *data)
{
	if (!has_sound) return;
	
	HRESULT err;

	DWORD play, write;
	err = soundbuffer->GetCurrentPosition(&play, &write);
	cl_assert(SUCCEEDED(err));

	int frag_bytes = frag_size*bytes_per_sample;

	int pos = (write / frag_bytes + 1) * frag_bytes;
	if (pos >= buffer_size) pos -= buffer_size;

	DWORD size1, size2;
	void *ptr1 = NULL, *ptr2 = NULL;
	err = soundbuffer->Lock(
		pos,
		frag_bytes,
		(void **) &ptr1,
		&size1,
		(void **) &ptr2,
		&size2,
		0);
	cl_assert(SUCCEEDED(err));
/*
	CL_Log::log(
		"debug", "#%1 play ptr: %2, write ptr: %3, pos: %4, size1: %5, size2: %6",
		pos / frag_bytes, play, write, pos, size1, size2);
*/
	char *_data = (char *) data;
	if (ptr1 != NULL) memcpy(ptr1, _data, size1);
	if (ptr2 != NULL) memcpy(ptr2, _data+size1, size2);

	err = soundbuffer->Unlock(ptr1, size1, ptr2, size2);
	cl_assert(SUCCEEDED(err));
}

void CL_SoundOutput_DirectSound::wait()
{
	if(!has_sound)
	{
		CL_System::sleep(100);
		return;
	}
	
	WaitForSingleObject(sleep_event, INFINITE);
	ResetEvent(sleep_event);
}

/////////////////////////////////////////////////////////////////////////////
// CL_SoundOutput_DirectSound implementation:
