/*
===========================================================================

Return to Castle Wolfenstein single player GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. 

This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).  

RTCW SP Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

RTCW SP Source Code 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/


// mac_snddma.c
// all other sound mixing is portable

#include "../client/snd_local.h"

#include <CoreServices/CoreServices.h>
#include <CoreAudio/AudioHardware.h>
#include <QuickTime/QuickTime.h>

// For 'ri'
#include "../renderer/tr_local.h"

#import <Foundation/NSData.h>
#import <Foundation/NSString.h>

static unsigned int submissionChunk;
static unsigned int maxMixedSamples;
static short       *s_mixedSamples;
static int s_chunkCount;                // number of chunks submitted
static qboolean s_isRunning;

static AudioDeviceID outputDeviceID;
static AudioStreamBasicDescription outputStreamBasicDescription;

/*
===============
audioDeviceIOProc
===============
*/

OSStatus audioDeviceIOProc( AudioDeviceID inDevice,
							const AudioTimeStamp *inNow,
							const AudioBufferList *inInputData,
							const AudioTimeStamp *inInputTime,
							AudioBufferList *outOutputData,
							const AudioTimeStamp *inOutputTime,
							void *inClientData ) {
	int offset;
	short        *samples;
	unsigned int sampleIndex;
	float        *outBuffer;
	float scale, temp;

	offset = ( s_chunkCount * submissionChunk ) % maxMixedSamples;
	samples = s_mixedSamples + offset;

	assert( outOutputData->mNumberBuffers == 1 );
	assert( outOutputData->mBuffers[0].mNumberChannels == 2 );
//    assert(outOutputData->mBuffers[0].mDataByteSize == (dma.submission_chunk * sizeof(float)));

	outBuffer = (float *)outOutputData->mBuffers[0].mData;

	// If we have run out of samples, return silence
	if ( s_chunkCount * submissionChunk > dma.channels * s_paintedtime ) {
		memset( outBuffer, 0, sizeof( *outBuffer ) * dma.submission_chunk );
	} else {
		scale = ( 1.0f / SHRT_MAX );
		if ( outputStreamBasicDescription.mSampleRate == 44100 ) {
			for ( sampleIndex = 0; sampleIndex < dma.submission_chunk; sampleIndex++ ) {
				// Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
				temp = samples[sampleIndex] * scale;
				outBuffer[( sampleIndex << 1 ) + 0] = temp;
				outBuffer[( sampleIndex << 1 ) + 1] = temp;
			}
		} else {
			for ( sampleIndex = 0; sampleIndex < dma.submission_chunk; sampleIndex++ ) {
				// Convert the samples from shorts to floats.  Scale the floats to be [-1..1].
				outBuffer[sampleIndex] = samples[sampleIndex] * scale;
			}
		}
	}

	s_chunkCount++; // this is the next buffer we will submit
	return 0;
}


/*
===============
S_MakeTestPattern
===============
*/
void S_MakeTestPattern( void ) {
	int i;
	float v;
	int sample;

	for ( i = 0 ; i < dma.samples / 2 ; i++ ) {
		v = sin( M_PI * 2 * i / 64 );
		sample = v * 0x4000;
		( (short *)dma.buffer )[i * 2] = sample;
		( (short *)dma.buffer )[i * 2 + 1] = sample;
	}
}

/*
===============
SNDDMA_Init
===============
*/
qboolean SNDDMA_Init( void ) {
	cvar_t *bufferSize;
	cvar_t *chunkSize;
	OSStatus status;
	UInt32 propertySize, bufferByteCount;

	if ( s_isRunning ) {
		return qtrue;
	}

	chunkSize = ri.Cvar_Get( "s_chunksize", "512", CVAR_ARCHIVE );
	bufferSize = ri.Cvar_Get( "s_buffersize", "16384", CVAR_ARCHIVE );
	Com_Printf( " Chunk size = %d\n", chunkSize->integer );
	Com_Printf( "Buffer size = %d\n", bufferSize->integer );

	if ( !chunkSize->integer ) {
		ri.Error( ERR_FATAL, "s_chunksize must be non-zero\n" );
	}
	if ( !bufferSize->integer ) {
		ri.Error( ERR_FATAL, "s_buffersize must be non-zero\n" );
	}
	if ( chunkSize->integer >= bufferSize->integer ) {
		ri.Error( ERR_FATAL, "s_chunksize must be less than s_buffersize\n" );
	}
	if ( bufferSize->integer % chunkSize->integer ) {
		ri.Error( ERR_FATAL, "s_buffersize must be an even multiple of s_chunksize\n" );
	}

	// Get the output device
	propertySize = sizeof( outputDeviceID );
	status = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID );
	if ( status ) {
		Com_Printf( "AudioHardwareGetProperty returned %d\n", status );
		return qfalse;
	}

	if ( outputDeviceID == kAudioDeviceUnknown ) {
		Com_Printf( "AudioHardwareGetProperty: outputDeviceID is kAudioDeviceUnknown\n" );
		return qfalse;
	}

	// Configure the output device
	propertySize = sizeof( bufferByteCount );
	bufferByteCount = chunkSize->integer * sizeof( float );
	status = AudioDeviceSetProperty( outputDeviceID, NULL, 0, NO, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount );
	if ( status ) {
		Com_Printf( "AudioDeviceSetProperty: returned %d when setting kAudioDevicePropertyBufferSize to %d\n", status, chunkSize->integer );
		return qfalse;
	}

	propertySize = sizeof( bufferByteCount );
	status = AudioDeviceGetProperty( outputDeviceID, 0, NO, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount );
	if ( status ) {
		Com_Printf( "AudioDeviceGetProperty: returned %d when setting kAudioDevicePropertyBufferSize\n", status );
		return qfalse;
	}

	// Print out the device status
	propertySize = sizeof( outputStreamBasicDescription );
	status = AudioDeviceGetProperty( outputDeviceID, 0, NO, kAudioDevicePropertyStreamFormat, &propertySize, &outputStreamBasicDescription );
	if ( status ) {
		Com_Printf( "AudioDeviceGetProperty: returned %d when getting kAudioDevicePropertyStreamFormat\n", status );
		return qfalse;
	}

	Com_Printf( "Hardware format:\n" );
	Com_Printf( "  %f mSampleRate\n", outputStreamBasicDescription.mSampleRate );
	Com_Printf( "  %c%c%c%c mFormatID\n",
				( outputStreamBasicDescription.mFormatID & 0xff000000 ) >> 24,
				( outputStreamBasicDescription.mFormatID & 0x00ff0000 ) >> 16,
				( outputStreamBasicDescription.mFormatID & 0x0000ff00 ) >>  8,
				( outputStreamBasicDescription.mFormatID & 0x000000ff ) >>  0 );
	Com_Printf( "  %5d mBytesPerPacket\n", outputStreamBasicDescription.mBytesPerPacket );
	Com_Printf( "  %5d mFramesPerPacket\n", outputStreamBasicDescription.mFramesPerPacket );
	Com_Printf( "  %5d mBytesPerFrame\n", outputStreamBasicDescription.mBytesPerFrame );
	Com_Printf( "  %5d mChannelsPerFrame\n", outputStreamBasicDescription.mChannelsPerFrame );
	Com_Printf( "  %5d mBitsPerChannel\n", outputStreamBasicDescription.mBitsPerChannel );

	if ( outputStreamBasicDescription.mFormatID != kAudioFormatLinearPCM ) {
		Com_Printf( "Default Audio Device doesn't support Linear PCM!" );
		return qfalse;
	}

	// Start sound running
	status = AudioDeviceAddIOProc( outputDeviceID, audioDeviceIOProc, NULL );
	if ( status ) {
		Com_Printf( "AudioDeviceAddIOProc: returned %d\n", status );
		return qfalse;
	}

	submissionChunk = chunkSize->integer;
	if ( outputStreamBasicDescription.mSampleRate == 44100 ) {
		submissionChunk = chunkSize->integer / 2;
	}
	maxMixedSamples = bufferSize->integer;
	s_mixedSamples = calloc( 1, sizeof( *s_mixedSamples ) * maxMixedSamples );
	Com_Printf( "Chunk Count = %d\n", ( maxMixedSamples / submissionChunk ) );

	// Tell the main app what we expect from it
	dma.samples = maxMixedSamples;
	dma.submission_chunk = submissionChunk;
	dma.samplebits = 16;
	dma.buffer = (byte *)s_mixedSamples;
	dma.channels = outputStreamBasicDescription.mChannelsPerFrame;
	dma.speed = 22050;  //(unsigned long)outputStreamBasicDescription.mSampleRate;

	// We haven't enqueued anything yet
	s_chunkCount = 0;

	status = AudioDeviceStart( outputDeviceID, audioDeviceIOProc );
	if ( status ) {
		Com_Printf( "AudioDeviceStart: returned %d\n", status );
		return qfalse;
	}

	s_isRunning = qtrue;

	return qtrue;
}

/*
===============
SNDDMA_GetBufferDuration
===============
*/
float SNDDMA_GetBufferDuration( void ) {
	return (float)dma.samples / (float)( dma.channels * dma.speed );
}

/*
===============
SNDDMA_GetDMAPos
===============
*/
int SNDDMA_GetDMAPos( void ) {
	return s_chunkCount * dma.submission_chunk;
}

/*
===============
SNDDMA_Shutdown
===============
*/
void SNDDMA_Shutdown( void ) {
	OSStatus status;

	if ( !s_isRunning ) {
		return;
	}

	status = AudioDeviceStop( outputDeviceID, audioDeviceIOProc );
	if ( status ) {
		Com_Printf( "AudioDeviceStop: returned %d\n", status );
		return;
	}

	s_isRunning = qfalse;

	status = AudioDeviceRemoveIOProc( outputDeviceID, audioDeviceIOProc );
	if ( status ) {
		Com_Printf( "AudioDeviceRemoveIOProc: returned %d\n", status );
		return;
	}

	free( s_mixedSamples );
	s_mixedSamples = NULL;
	dma.samples = NULL;
}

/*
===============
SNDDMA_BeginPainting
===============
*/
void SNDDMA_BeginPainting( void ) {
}

/*
===============
SNDDMA_Submit
===============
*/
void SNDDMA_Submit( void ) {
}
