///////////////////////////////////////////////////////////////////////////////
/// @file deOGG.cpp
///
/// @brief Destiny Sound System OGG Bit Stream
///
/// @author paradoxnj
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jan 2002
/// @author paradoxnj
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include <windows.h> // for various stuff
#include "deOGG.hpp"
#include "deSound.hpp"
#include "deFileSystem.hpp"
#include "deFile.hpp"

#ifdef _DEBUG
#	pragma comment(lib, "ogg_static_d")
#	pragma comment(lib, "vorbis_static_d")
#else
#	pragma comment(lib, "ogg_static")
#	pragma comment(lib, "vorbis_static")
#endif

#pragma todo (deOGG::GetLength)
u32 deOGG::GetLength()
{
	return 10000000;
}

deOGG::deOGG() : deSoundData()
{
	m_BufferSize = 0;
	m_CurrWritePos = 0;
	m_EmptyPos = (DWORD)-1;
	m_FillLength = 0;
	m_FillPos = 0;
	m_SecureDistance = 0;
	m_pSource = NULL;

	m_InterfaceType = IdeSoundData::iface_OGG;
	m_RefCount = 1;

	ZeroMemory(&m_Format, sizeof(WAVEFORMATEX));
}

deOGG::deOGG(char *filename) : deSoundData(filename)
{
	m_BufferSize = 0;
	m_CurrWritePos = 0;
	m_EmptyPos = (DWORD)-1;
	m_FillLength = 0;
	m_FillPos = 0;
	m_SecureDistance = 0;
	m_pSource = NULL;

	m_InterfaceType = IdeSoundData::iface_OGG;
	m_RefCount = 1;

	ZeroMemory(&m_Format, sizeof(WAVEFORMATEX));

	Initialize(filename);
}

deOGG::~deOGG()
{
	Destroy();
}

void *deOGG::GetInterface(int interface_id)
{
	if (interface_id == IdeSoundData_GetInterfaceID())
		return (IdeSoundData*)this;

	return NULL;
}

deBoolean deOGG::Initialize(const char *filename)
{
	char				*buffer;
	int					bytes;

	//Create a DOS file class
	m_pSourceSystem = IdeFileSystem_CreateFSReal();
	if (!m_pSourceSystem)
		return deFALSE;

	//Open the file
	m_pSource = m_pSourceSystem->Open((const char*)filename, deFile_Open_Existing | deFile_Open_Read);
	if (!m_pSource)
	{
		DE3D_SAFE_RELEASE(m_pSourceSystem);
		return deFALSE;
	}

	//Seek to the beginning of the file
	m_pSource->SetPosition(0, deFile_Seek_Start);

	//Initialize the framer
	ogg_sync_init(&m_oy);

	//Create an OGG buffer to hold 4K of raw data
	buffer = ogg_sync_buffer(&m_oy, 4096);

	//Read some data into the buffer
	bytes = m_pSource->Read((void*)buffer, 4096);

	//We have to have some data to proceed.  If not this is an invalid file...
	if (bytes == 0)
	{
		Destroy();
		return deFALSE;
	}

	//Validate the data
	ogg_sync_wrote(&m_oy, bytes);

	//Get the first page.  If the page is no good, it must not be Vorbis data
	if (ogg_sync_pageout(&m_oy, &m_og) != 1)
	{
		Destroy();
		return deFALSE;
	}

	//Initialize the logical stream with the serial number of the OGG file
	ogg_stream_init(&m_os, ogg_page_serialno(&m_og));

	//Initialize the Vorbis Info struct
	vorbis_info_init(&m_vi);

	//Initialize the Vorbis Comment struct
	vorbis_comment_init(&m_vc);

	//Read the first page into the logical stream. If no good, then the versions may be mismatched...
	if (ogg_stream_pagein(&m_os, &m_og) < 0)
	{
		Destroy();
		return deFALSE;
	}

	//Read the initial packet header.  If no good, it must not be Vorbis data
	if (!ogg_stream_packetout(&m_os, &m_op))
	{
		Destroy();
		return deFALSE;
	}

	//Read in the Vorbis header.  If not good then it must not be a Vorbis header
	if (vorbis_synthesis_headerin(&m_vi, &m_vc, &m_op) < 0)
	{
		Destroy();
		return deFALSE;
	}

	/* At this point, we're sure we're Vorbis.  We've set up the logical
	   (Ogg) bitstream decoder.  Get the comment and codebook headers and
	   set up the Vorbis decoder */
    
	/* The next two packets in order are the comment and codebook headers.
	   They're likely large and may span multiple pages.  Thus we reead
	   and submit data until we get our two pacakets, watching that no
	   pages are missing.  If a page is missing, error out; losing a
	   header page is the only place where missing data is fatal. */

	int i = 0;
	while (i < 2)
	{
		while (i < 2)
		{
			//Get the next page
			int result = ogg_sync_pageout(&m_oy, &m_og);
			switch (result)
			{
			case 0:
				{
					//We need more data
					break;
				}
			case 1:
				{
					//Read the page into the logical bit stream
					ogg_stream_pagein(&m_os, &m_og);

					while (i < 2)
					{
						result = ogg_stream_packetout(&m_os, &m_op);
						switch (result)
						{
						case 0:
							{
								//Data is no good
								break;
							}
						case -1:
							{
								//Data is corrupt
								Destroy();
								return deFALSE;
								break;
							}
						}

						vorbis_synthesis_headerin(&m_vi, &m_vc, &m_op);
						i++;
					}
				}
			}
		}

		//Add more to the buffer.  4K at a time
		buffer = ogg_sync_buffer(&m_oy, 4096);

		//Read 4K from the file
		bytes = m_pSource->Read((void*)buffer, 4096);
		if (bytes == 0 && i < 2)
		{
			//Ahhhhhh.....End of file before getting the Vorbis headers!!!!
			Destroy();
			return deFALSE;
		}

		//Verify the data
		ogg_sync_wrote(&m_oy, bytes);
	}

	//Initialize the synthesizer
	vorbis_synthesis_init(&m_vs, &m_vi);

	//Initialize the Vorbis block
	vorbis_block_init(&m_vs, &m_vb);

	//Ready for streaming
	return deTRUE;
}

void deOGG::Destroy()
{
	//Clear the OGG Bit stream
	ogg_stream_clear(&m_os);

	//Clear the Vorbis block
	vorbis_block_clear(&m_vb);

	//Clear the Vorbis DSP
	vorbis_dsp_clear(&m_vs);

	//Clear the Vorbis Info.  This must always be called last
	vorbis_info_clear(&m_vi);

	//Clean up the framer
	ogg_sync_clear(&m_oy);

	//Close the source file
	m_pSource->Close();

	//Destroy the Source File class
	DE3D_SAFE_RELEASE(m_pSource);
	DE3D_SAFE_RELEASE(m_pSourceSystem);
}

void deOGG::GetFormat(WAVEFORMATEX *format)
{
	DE_ASSERT(format != NULL);
	ZeroMemory(format, sizeof(WAVEFORMATEX));

	format->cbSize = 0;
	format->nSamplesPerSec = m_vi.rate;
	format->nChannels = (unsigned short)m_vi.channels;
	format->wBitsPerSample = 16;
	format->wFormatTag = WAVE_FORMAT_PCM;
	format->nBlockAlign = (unsigned short)(format->wBitsPerSample * format->nChannels / 8);
	format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
}

int deOGG::GetDataChunk(void *ptr, long size, s32 DataOffset)
{
	//Convert the data to short values
	size /= sizeof(short) * m_vi.channels;

	short *data_ptr = (short*)ptr;

	//Compute the amount of data left to process
	int data_left = size;

	//Get the data
	while (!AtEndOfStream() && data_left > 0)
	{
		float				**pcm;

		//Convert the logical bitstream to PCM data.  Save the samples
		int samples = vorbis_synthesis_pcmout(&m_vs, &pcm);

		//Uh..Oh.  No samples.  Let's see if there are any in the next chunk.
		if (samples <= 0)
		{
			while (!AtEndOfStream())
			{
				//Put the next packet into the logical bitstream.
				int result = ogg_stream_packetout(&m_os, &m_op);
				if (result > 0)
				{
					if(vorbis_synthesis(&m_vb, &m_op) == 0) /* test for success! */
						vorbis_synthesis_blockin(&m_vs, &m_vb);
					
					break;
				}
		
				while (!AtEndOfStream())
				{
					//Get the next packet
					int result = ogg_sync_pageout(&m_oy, &m_og);
					if (result == -1) 
						continue; // corrupt data at this page position. Read next.

					if (result == 0) // need more data
					{
						char *buffer = ogg_sync_buffer(&m_oy, 4096);
						int bytes = m_pSource->Read((void*)buffer, 4096);
						ogg_sync_wrote(&m_oy, bytes);
						if (bytes == 0)
							return 0;
					}
					else
					{
						//All is good
						ogg_stream_pagein(&m_os, &m_og);
						break;
					}
				}
			}

			continue;
		}

		if (samples > data_left)
			samples = data_left;

		for (int i = 0; i < samples; i++)
		{
			for (int j = 0; j < m_vi.channels; j++)
			{
				int s = (int)(pcm[j][i] * 32767);
				if (s < -32767) s = -32767;
				else if (s > 32767) s = 32767;
				*(data_ptr++) = (short) s;
			}
		}
		
		//Read the samples into the synthesizer.
		vorbis_synthesis_read(&m_vs, samples);
		data_left -= samples;
	}

	return (size - data_left) * sizeof(short) * m_vi.channels;
}

IdeResourceBase *deOGG::MakeCopy()
{
	return (IdeResourceBase*)new deOGG((char*)GetFilename());
}

uint8 *deOGG::GetAllData()
{
	return NULL;
}

deBoolean deOGG::AtEndOfStream()
{
	if (m_pSource->GetPosition() >= m_pSource->GetSize())
		return deTRUE;
	else
		return deFALSE;
}

void deOGG::SetPosition(long NewPos)
{
#pragma todo (deOGG::SetPosition)
	DE_ASSERT(NewPos != NewPos+1); // this isn't implemented
}