/***********************************************************************
 * VirtualDub Modification for OGM
 *
 * Copyright (C) 2002 Cyrius
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *   
 * This program 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 this program (see the file COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * or visit http://www.gnu.org/copyleft/gpl.html
 *
 ***********************************************************************
 *
 *
 *
 */
#define INITGUID
//	Pulco 29/10/2002 : this avoids duplicate guid symbols with VC++ 6.0
//	cf oggds.h


#include <crtdbg.h>
#include <math.h>

#include <windows.h>

#include "OGMReadHandler.h"


#ifdef _DEBUG
#endif

extern CRITICAL_SECTION g_diskcs;
extern bool g_disklockinited;


//	Pulco 28/10/2002
//	for compilation under VC++ 6.0
#ifndef _WAVEFORMATEXTENSIBLE_
#define _WAVEFORMATEXTENSIBLE_
#pragma pack(1)
typedef struct {
    WAVEFORMATEX    Format;
    union {
        WORD wValidBitsPerSample;       /* bits of precision  */
        WORD wSamplesPerBlock;          /* valid if wBitsPerSample==0 */
        WORD wReserved;                 /* If neither applies, set to zero. */
    } Samples;
    DWORD           dwChannelMask;      /* which channels are */
                                        /* present in stream  */
    GUID            SubFormat;
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
#pragma pack()
#endif // !_WAVEFORMATEXTENSIBLE_
//	VC++ 6.0 does not know this by default either:  
#ifndef SPEAKER_FRONT_LEFT
#define SPEAKER_FRONT_LEFT 0x1 
#define SPEAKER_FRONT_RIGHT 0x2 
#define SPEAKER_FRONT_CENTER 0x4 
#define SPEAKER_LOW_FREQUENCY 0x8 
#define SPEAKER_BACK_LEFT 0x10 
#define SPEAKER_BACK_RIGHT 0x20 
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 
#define SPEAKER_BACK_CENTER 0x100 
#define SPEAKER_SIDE_LEFT 0x200 
#define SPEAKER_SIDE_RIGHT 0x400 
#define SPEAKER_TOP_CENTER 0x800 
#define SPEAKER_TOP_FRONT_LEFT 0x1000 
#define SPEAKER_TOP_FRONT_CENTER 0x2000 
#define SPEAKER_TOP_FRONT_RIGHT 0x4000 
#define SPEAKER_TOP_BACK_LEFT 0x8000 
#define SPEAKER_TOP_BACK_CENTER 0x10000 
#define SPEAKER_TOP_BACK_RIGHT 0x20000 
#endif






///////////////////////////////////////////////////////////////////////////

IAVIReadHandler *CreateOGMReadHandler(char *s) {
	return new OGMReadHandler(s);
}

///////////////////////////////////////////////////////////////////////////

OGMReadStream::OGMReadStream(OGMReadHandler *parent, streamNode *stream) {
	this->parent = parent;
	this->stream = stream;
	firstKey = currentKey = stream->keys.AtHead();
	lastKey = stream->keys.AtTail();
	can_cut_first_packet = false;

	ogg_sync_init(&oss);
	if(ogg_stream_init(&sstate, stream->serial))
		throw MyError("Could not initialize the ogg_stream_state");

	lastPage = stream->pages.AtTail();

	// Find the first Page containing data
	pageNode *page = stream->pages.AtHead();
	int n = page->nb_packets;
	int n2 = 0;
	while( (n<=stream->num_headers) && page && page->NextFromHead()) {
		n2 += page->nb_packets;
		page = page->NextFromHead();
		n += page->nb_packets;
	}

	// Oops the first data packet may not be the first packet in the Page :(
	first_packet = stream->num_headers - n2;

	if(n<=stream->num_headers)
		throw MyError("Invalid Ogg Media Stream");

	firstData = currentPage = page;

	length = stream->n_samples;

	format = NULL;
	format_size = 0;
	if(stream->type == S_VIDEO) {
		format_size = sizeof(BITMAPINFOHEADER);
		format = malloc(format_size);
		if(!format)
			throw MyMemoryError();

		memset(format, 0, format_size);

		BITMAPINFOHEADER *bi = (BITMAPINFOHEADER *)format;
		bi->biBitCount = stream->sh.bits_per_sample;
		memcpy(&bi->biCompression, stream->sh.subtype, 4);
		bi->biHeight = stream->sh.sh.video.height;
		bi->biPlanes = 1;
		bi->biSize = format_size;
		bi->biWidth = stream->sh.sh.video.width;
	} else if(stream->type == S_AUDIO) {
		format_size = sizeof(WAVEFORMATEX);
		format = malloc(format_size);

		if(!format)
			throw MyMemoryError();

		memset(format, 0, format_size);

		WAVEFORMATEX *wf = (WAVEFORMATEX *)format;
		wf->cbSize = 0;
		wf->nAvgBytesPerSec = stream->sh.sh.audio.avgbytespersec;
		wf->nBlockAlign = stream->sh.sh.audio.blockalign;
		wf->nChannels = stream->sh.sh.audio.channels;
		wf->nSamplesPerSec = stream->sample_rate;
		wf->wBitsPerSample = stream->sh.bits_per_sample;
		char tag[5];
		memcpy(tag, stream->sh.subtype, 4);
		tag[4] = 0;
		wf->wFormatTag = strtoul(tag, NULL, 16);
	} else if(stream->type == S_VORBIS) {
		format_size = sizeof(WAVEFORMATEXTENSIBLE);
		format = malloc(format_size);

		if(!format)
			throw MyMemoryError();

		memset(format, 0, format_size);

		WAVEFORMATEXTENSIBLE *wfe = (WAVEFORMATEXTENSIBLE *)format;
		wfe->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
		wfe->Format.nAvgBytesPerSec = stream->sh.sh.audio.avgbytespersec;
		wfe->Format.nBlockAlign = stream->sh.sh.audio.blockalign;
		wfe->Format.nChannels = stream->sh.sh.audio.channels;
		wfe->Format.nSamplesPerSec = stream->sample_rate;
		// According to MSDN ...
		wfe->Samples.wValidBitsPerSample = wfe->Format.wBitsPerSample = stream->sh.bits_per_sample;
		// Sorry I don't know aything about multiple speakers :p
		// If someone know what should be set here, please tell me
		// (anyway I don't think anything will use those settings afterwards ....)
		switch(stream->sh.sh.audio.channels) {
			case 2:
				wfe->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
				break;
			case 4:
				wfe->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT
					| SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
				break;
			case 5:
				wfe->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT
					| SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT
					| SPEAKER_LOW_FREQUENCY;
				break;
			case 6:
				wfe->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT
					| SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT
					| SPEAKER_LOW_FREQUENCY | SPEAKER_FRONT_CENTER;
				break;
		}
		wfe->SubFormat = MEDIASUBTYPE_Vorbis;
	} else if(stream->type == S_TEXT) {
		// Let's use this text stream as an audio one :p
		format_size = sizeof(WAVEFORMATEX);
		format = malloc(format_size);

		if(!format)
			throw MyMemoryError();

		memset(format, 0, format_size);

		WAVEFORMATEX *wf = (WAVEFORMATEX *)format;
		wf->cbSize = 0;
		wf->nAvgBytesPerSec = 1;
		wf->nBlockAlign = 1;
		wf->nChannels = 1;
		wf->nSamplesPerSec = 1000;
		wf->wBitsPerSample = 8;
		wf->wFormatTag = 0x0000;
	}

	parent->AddRef();
}

OGMReadStream::~OGMReadStream() {
	parent->Release();
}

HRESULT OGMReadStream::BeginStreaming(long lStart, long lEnd, long lRate) {
	return 0;
}

HRESULT OGMReadStream::EndStreaming() {
	return 0;
}

HRESULT OGMReadStream::Info(AVISTREAMINFO *pasi, long lSize) {
	AVISTREAMINFO asi;

	memset(&asi, 0, sizeof asi);

	asi.fccType				= (stream->type==S_VIDEO ? streamtypeVIDEO : stream->type==S_TEXT ? streamtypeTEXT : streamtypeAUDIO);
	memcpy(&asi.fccHandler, stream->sh.subtype, 4);
	asi.dwFlags				= 0;
	asi.wPriority			= 0;
	asi.wLanguage			= 0;
	if(stream->type == S_VIDEO) {
		asi.dwScale				= stream->sh.time_unit;
		asi.dwRate				= 10000000;
	} else if(stream->type == S_TEXT) {
		asi.dwScale				= 1;
		asi.dwRate				= 1000;
	} else {
		asi.dwScale				= 1;
		asi.dwRate				= stream->sh.samples_per_unit;
	}
	asi.dwStart				= 0;
	asi.dwLength			= length;
	asi.dwInitialFrames	= 0;
	// If the max size we found in the stream is greater than the buffersize
	// suggested in the header, take it
	if(stream->max_packet_size > stream->sh.buffersize) {
		_RPT3(_CRT_WARN, "Suggested buffersize of stream %d (%d) below max size found (%d)\n"
			, stream->serial, stream->sh.buffersize, stream->max_packet_size);
		asi.dwSuggestedBufferSize = stream->max_packet_size;
	} else {
		asi.dwSuggestedBufferSize = stream->sh.buffersize;
	}
	asi.dwQuality			= 0;
	asi.dwSampleSize		= 0;
	if(stream->type == S_VIDEO) {
		asi.rcFrame.top			= 0;
		asi.rcFrame.left		= 0;
		asi.rcFrame.right		= stream->sh.sh.video.width;
		asi.rcFrame.bottom		= stream->sh.sh.video.height;
	}

	if (lSize < sizeof asi)
		memcpy(pasi, &asi, lSize);
	else {
		memcpy(pasi, &asi, sizeof asi);
		memset((char *)pasi + sizeof asi, 0, lSize - sizeof asi);
	}

	return 0;
}

bool OGMReadStream::IsKeyFrame(long lFrame) {
	// Find where is the nearest of our pointers (first, current or last)
	if(lFrame < (currentKey->granulepos / 2) )
		currentKey = firstKey;
	else if(lFrame > (lastKey->granulepos + currentKey->granulepos)/2 )
		currentKey = lastKey;

	if(currentKey->granulepos == lFrame)
		return true;

	if(currentKey->granulepos<lFrame) {
		// Go forward
		while((currentKey != lastKey) && (currentKey->granulepos<lFrame)) {
			currentKey = currentKey->NextFromHead();
			if(currentKey->granulepos == lFrame)
				return true;
		}
	} else {
		// Go backward
		while((currentKey != firstKey) && (currentKey->granulepos>lFrame)) {
			currentKey = currentKey->NextFromTail();
			if(currentKey->granulepos == lFrame)
				return true;
		}
	}

	return false;
}

__int64 OGMReadStream::FramePos(long lStart) {
	if(lStart < 0)
		return 0;
	if(lStart >= length) {
		if(stream && lastPage)
			return (lastPage->file_pos & ((unsigned __int64)0x7FFFFFFFFFFFFFFF));
		else
			return 0;
	}

	//Find our nearest pointer (first, current or last)
	if(lStart < currentPage->granulepos / 2)
		currentPage = firstData;
	else if(lStart > (lastPage->granulepos + currentPage->granulepos) / 2)
		currentPage = lastPage;

	while((currentPage != firstData) && (currentPage->granulepos>lStart) )
		currentPage = currentPage->NextFromTail();
	// Go forward
	while((currentPage != lastPage) && (currentPage->NextFromHead()->granulepos<=lStart) )
		currentPage = currentPage->NextFromHead();

	// 13/11/2002, Cyrius : let's be more accurate and count packets preceding the frame we want in the page
	__int64 pos = (currentPage->file_pos & ((unsigned __int64)0x7FFFFFFFFFFFFFFF));
	lStart -= currentPage->granulepos;
	if((lStart>0) && (lStart<=currentPage->nb_packets))
		while(lStart--)
			pos += currentPage->n_bytes[lStart];

	return pos;
}

HRESULT OGMReadStream::Read(long lStart, long lSamples, void *lpBuffer, long cbBuffer, long *plBytes, long *plSamples) {
	if( (lStart < 0) || (lStart >= length) ) {
		if(plBytes)
			*plBytes = 0;
		if(plSamples)
			*plSamples = 0;

		return 0;
	}

	//Find our nearest pointer (first, current or last)
	if(lStart < currentPage->granulepos / 2)
		currentPage = firstData;
	else if(lStart > (lastPage->granulepos + currentPage->granulepos) / 2)
		currentPage = lastPage;

	// Doing this way we are sure that the current page now contains
	// at least one Packet, and especially contains the Packet we
	// want
	// Go backward
	while((currentPage != firstData) && (currentPage->granulepos>lStart) )
		currentPage = currentPage->NextFromTail();
	// Go forward
	while((currentPage != lastPage) && (currentPage->NextFromHead()->granulepos<=lStart) )
		currentPage = currentPage->NextFromHead();

	// currentPage contains the Packet corresponding to lStart
	// if the granulepos of the Packet following the first one in
	// this Page exceed lStart, this means we want the first Packet.
	// And in this case we must verify if previous pages
	// do not contain this (possibly) spanned packet
	// the 64th bit of file_pos tell if this is a continued page
	if( (currentPage->granulepos+currentPage->n_samples[0]>lStart) && (currentPage->file_pos>>63) ) {
		currentPage = currentPage->NextFromTail();
		while((currentPage->file_pos>>63) && (!currentPage->nb_packets))
			currentPage = currentPage->NextFromTail();
	}

	int n_toread = lSamples;
	if(n_toread == AVISTREAMREAD_CONVENIENT)
		n_toread = 1;

	int n_read = 0;
	int packets_to_skip = 0;
	// Determines how many packets we must skip before reaching the wanted one
	ogg_int64_t gpos = currentPage->granulepos;
	// For the first sample, the packet may not be the first in the page
	// The problem : Page's granulepos = 0, but first Packet may be header or comment :(
	// Fortunately we prepared ourself for this case :)
	if(!lStart)
		packets_to_skip = first_packet;
	if(can_cut_first_packet) {
		// We can "cut" the Packet (i.e. send data but lower the number of
		// samples it contains according to where the Packet start and lStart)
		// So we must get the Packet that contains lStart
		while((packets_to_skip<currentPage->nb_packets) && (gpos+currentPage->n_samples[packets_to_skip]<=lStart)) {
			gpos += currentPage->n_samples[packets_to_skip];
			packets_to_skip++;
		}
	} else {
		// We cannot cut, so we must get the first Packet just after lStart
		while( (gpos < lStart) && (packets_to_skip<currentPage->nb_packets) ) {
			gpos += currentPage->n_samples[packets_to_skip];
			packets_to_skip++;
		}
	}

	// If we couldn't reach the exact granulepos, it means that frame lStart
	// is in the middle of a Packet, i.e. it is a dropped frame
	// (this apply to video)
	if( (stream->type == S_VIDEO) && (gpos != lStart)) {
		if(plBytes)
			*plBytes = 0;
		if(plSamples)
			*plSamples = 0;
		return 0;
	}

	int s_lag = 0;
	if(can_cut_first_packet)
		s_lag = lStart - gpos;

#ifdef _DEBUG
	if(s_lag) {
		s_lag = s_lag;
	}
#endif

	// Get the total length of data
	int skipped = packets_to_skip;
	int pkt_num = 0;
	pageNode *c_page = currentPage;
	long size = 0;
	// Start by skipping unwanted packets
	while(skipped--) {
		if(pkt_num < c_page->nb_packets) {
			pkt_num++;
		} else {
			pkt_num = 0;
			c_page = c_page->NextFromHead();
		}
	}
	int samples_to_read = n_toread;
	while(samples_to_read>0) {
		if(pkt_num < c_page->nb_packets) {
			size += c_page->n_bytes[pkt_num];
			samples_to_read -= c_page->n_samples[pkt_num];
			// One Packet at a time
			if( /*((stream->type == S_VORBIS)||(stream->type == S_TEXT)) && */(c_page->n_samples[pkt_num]) )
				break;
			pkt_num++;
		} else {
			pkt_num = 0;
			if(c_page != lastPage)
				c_page = c_page->NextFromHead();
			while(!c_page->nb_packets && (c_page!=lastPage))
				c_page = c_page->NextFromHead();
			if(!c_page->nb_packets && (c_page==lastPage))
				break;
		}
	}

	// If the buffer is too small
	if(lpBuffer && (cbBuffer < size) ) {
		return AVIERR_BUFFERTOOSMALL;
	}

	// If we wanted to know the size of the buffer
	if(!lpBuffer) {
		if(plBytes)
			*plBytes = size;
		if(plSamples) {
			// This is a video stream, and VDub asked us to read the convenient number of frames ...
			// Wa may have found dropped frames, but VDub do not have to know that yet (it wiil
			// know that later anyway), so we say we read 1 sample (which is true)
			if( (stream->type==S_VIDEO) && (lSamples==AVISTREAMREAD_CONVENIENT) )
				*plSamples = 1;
			else
				//n_toread-samples_to_read give exactly how many samples we read
				*plSamples = (n_toread-samples_to_read) - s_lag;
		}

		return 0;
	}

	// If we must skip packets, verify that the first one will be
	// outputted (it is not the case if this packet is a spanned one)
	pkt_num = 0;
	if(packets_to_skip && (currentPage->file_pos>>63)) {
		packets_to_skip--;
		pkt_num = 1;
	}
	// Now we must read the samples, let's start
	// First reset the ogg_sync_state (because we will seek)
	ogg_sync_reset(&oss);
	ogg_stream_reset(&sstate);
	// Set position in file
	//unsigned __int64 pos = currentPage->file_pos & ((unsigned __int64)0x7FFFFFFFFFFFFFFF);
	parent->SeekFile(currentPage->file_pos & ((unsigned __int64)0x7FFFFFFFFFFFFFFF));
	long bytes_read = 0;
	int samples_read = 0;
	char *lpBufferPos = (char *)lpBuffer;
	ogg_page page;
	ogg_packet packet;
	int hdrlen;
	while(true) {
		if(_getNextPage(&page, currentPage->size)<=0)
			break;

		// If this page belongs to another stream, skip it
		if(ogg_page_serialno(&page) != stream->serial)
			continue;

		if(ogg_stream_pagein(&sstate, &page))
			throw MyError("ogg_stream_pagein failed");

		while(ogg_stream_packetout(&sstate, &packet)==1) {
			// If we have packets to skip ...
			if(samples_read >= n_toread)
				break;
			else if(packets_to_skip)
				packets_to_skip--;
			else {
				if(stream->type != S_VORBIS) {
					// We should believe the information gathered before
					// But who knows ... at least verify lenght of data
					hdrlen = (*packet.packet & PACKET_LEN_BITS01) >> 6;
					hdrlen |= (*packet.packet & PACKET_LEN_BITS2) << 1;
					memcpy(lpBufferPos, packet.packet+1+hdrlen, packet.bytes-1-hdrlen);
					samples_read += currentPage->n_samples[pkt_num];
					bytes_read += (packet.bytes-1-hdrlen);
					lpBufferPos += (packet.bytes-1-hdrlen);
					/*if(stream->type == S_TEXT)*/
						break;
				} else {
					memcpy(lpBufferPos, packet.packet, packet.bytes);
					samples_read += currentPage->n_samples[pkt_num];
					bytes_read += packet.bytes;
					lpBufferPos += packet.bytes;
					// Only one packet at a time
					break;
				}
			}
			pkt_num++;
		}
		pkt_num = 0;

		// Only one packet at a time
		if(/*((stream->type == S_VORBIS)||(stream->type == S_TEXT)) && */(samples_read))
			break;

		// Test if we have reached lSamples now
		// it is best to test it now since we just ended the previous while loop
		// and we may have ended it because we get the correct number of samples
		// (so no need to go to next page in ths case)
		/*if(samples_read >= n_toread)
			break;*/

		// Just in case : verify we haven't reached the end of the stream
		// (maybe some stupid guy asked us more samples than what remain after lStart ;))
		// Nb : if currentPage is set to NULL wherever here, this would be fatal
		// in VDub for the next operation ...
		if(currentPage == lastPage)
			break;
		currentPage = currentPage->NextFromHead();
	}

	if(plBytes)
		*plBytes = bytes_read;
	if(plSamples)
		*plSamples = samples_read - s_lag;

#ifdef _DEBUG
	if(samples_read - s_lag == 0) {
		return 0;
	}
#endif
	return 0;
}

long OGMReadStream::Start() {
	return 0;
}

long OGMReadStream::End() {
	return length;
}

long OGMReadStream::PrevKeyFrame(long lFrame) {
	if(lFrame <= firstKey->granulepos)
		return -1;
	else if(lFrame > lastKey->granulepos)
		return lastKey->granulepos;

	// Find where is the nearest of our pointers (first, current or last)
	if(lFrame < (currentKey->granulepos / 2) )
		currentKey = firstKey;
	else if(lFrame > (lastKey->granulepos + currentKey->granulepos)/2 )
		currentKey = lastKey;

	if(currentKey->granulepos == lFrame) {
		if(currentKey == firstKey)
			return -1;
		else
			return currentKey->NextFromTail()->granulepos;
	}

	if(currentKey->granulepos<lFrame) {
		// Go forward, stop at previous key_frame
		while((currentKey!=lastKey) && (currentKey->NextFromHead()->granulepos<lFrame))
			currentKey = currentKey->NextFromHead();
		return currentKey->granulepos;
	} else {
		// Go backward
		while((currentKey!=firstKey) && (currentKey->granulepos>=lFrame))
			currentKey = currentKey->NextFromTail();
		// Verify that it is still good (there could be no previous keyframe than lFrame)
		if(currentKey->granulepos>=lFrame)
			return -1;
		return currentKey->granulepos;
	}
}

long OGMReadStream::NextKeyFrame(long lFrame) {
	if(lFrame < firstKey->granulepos)
		return firstKey->granulepos;
	else if(lFrame >= lastKey->granulepos)
		return -1;

	// Find where is the nearest of our pointers (first, current or last)
	if(lFrame < (currentKey->granulepos / 2) )
		currentKey = firstKey;
	else if(lFrame > (lastKey->granulepos + currentKey->granulepos)/2 )
		currentKey = lastKey;

	if(currentKey->granulepos == lFrame) {
		if(currentKey == lastKey)
			return -1;
		else
			return currentKey->NextFromHead()->granulepos;
	}

	if(currentKey->granulepos<lFrame) {
		// Go forward
		while((currentKey!=lastKey) && (currentKey->granulepos<=lFrame))
			currentKey = currentKey->NextFromHead();
		// Verify that it is still good (there could be no next keyframe than lFrame)
		if(currentKey->granulepos<=lFrame)
			return -1;
		return currentKey->granulepos;
	} else {
		// Go backward
		while((currentKey!=firstKey) && (currentKey->NextFromTail()->granulepos>lFrame))
			currentKey = currentKey->NextFromTail();
		return currentKey->granulepos;
	}
}

// Warning, don't think you know what this function does only by reading its name
// This function must return the nearest (previous) key-frame (and not the absolute
// nearest one)
long OGMReadStream::NearestKeyFrame(long lFrame) {
	if(IsKeyFrame(lFrame))
		return lFrame;

	long lprev = PrevKeyFrame(lFrame);

	if(lprev < 0)
		return -1;
	else
		return lprev;
}

HRESULT OGMReadStream::FormatSize(long lFrame, long *plSize) {
	if(plSize)
		*plSize = format_size;
	return 0;
}

HRESULT OGMReadStream::ReadFormat(long lFrame, void *pFormat, long *plSize) {
	if(!pFormat) {
		*plSize = format_size;
	}

	if(*plSize < format_size) {
		memcpy(pFormat, format, *plSize);
	} else {
		memcpy(pFormat, format, format_size);
		*plSize = format_size;
	}

	return 0;
}

bool OGMReadStream::isStreaming() {
	return false;
}

bool OGMReadStream::isKeyframeOnly() {
  // Oh well I don't want to test that (anyway I didn't find any way to include
	// an uncompressed video stream in OGM so ...)
  return false;
}

bool OGMReadStream::getVBRInfo(double& bitrate_mean, double& bitrate_stddev, double& maxdev) {
	// No there are no VBR stream in this file hey hey :p
	return false;
}

void OGMReadStream::sendHeaders(Packetizer *packetizer) {
	if(!packetizer)
		return;

	pageNode *page_l = stream->pages.AtHead();
	int to_process = stream->num_headers;
	ogg_sync_reset(&oss);
	ogg_stream_reset(&sstate);
	parent->SeekFile(page_l->file_pos & ((unsigned __int64)0x7FFFFFFFFFFFFFFF));
	ogg_page page;
	ogg_packet packet;
	bool first = true;
	bool comments_sent = false;
	while(to_process>0) {
		if(_getNextPage(&page, page_l->size)<=0)
			break;

		if(ogg_stream_pagein(&sstate, &page))
			throw MyError("ogg_stream_pagein failed");

		while(ogg_stream_packetout(&sstate, &packet)==1) {
			if(packet.packetno == 1) {
				comments_sent = true;
				/*// Replace the comments
				vorbis_comment vc;
				vorbis_comment_init(&vc);
				comment_list *comment = stream->comments;
				while(comment) {
					if(comment->tag)
						vorbis_comment_add_tag(&vc, comment->tag, comment->comment);
					else
						vorbis_comment_add(&vc, comment->comment);
					comment = comment->next;
				}
				vorbis_commentheader_out(&vc, &packet);*/
				packetizer->produce_comment_packet(stream->comments, first || (to_process==1));
			} else
				packetizer->process_packet(&packet, first || (to_process==1));
			first = false;
			if(!--to_process)
				break;
		}
		if(to_process<=0)
			break;

		if(!page_l->next)
			break;
		page_l = page_l->NextFromHead();
		parent->SeekFile(page_l->file_pos & ((unsigned __int64)0x7FFFFFFFFFFFFFFF));
	}
	// No comments in the original stream, but we have comments now
	if(stream->comments && !comments_sent) {
		/*// Add the comments
		vorbis_comment vc;
		vorbis_comment_init(&vc);
		comment_list *comment = stream->comments;
		while(comment) {
			if(comment->tag)
				vorbis_comment_add_tag(&vc, comment->tag, comment->comment);
			else
				vorbis_comment_add(&vc, comment->comment);
			comment = comment->next;
		}
		vorbis_commentheader_out(&vc, &packet);
		packetizer->process_packet(&packet, true);*/
		packetizer->produce_comment_packet(stream->comments, true);
	}
}

comment_list *OGMReadStream::getComments(void) {
	comment_list *first_comment = NULL;
	comment_list *comment = NULL;
	for(int i=0 ; i<stream->vc.comments ; i++) {
		if(first_comment) {
			comment->next = new comment_list;
			if(!comment->next)
				throw MyMemoryError();
			comment = comment->next;
		} else {
			first_comment = comment = new comment_list;
			if(!comment)
				throw MyMemoryError();
		}
		bool has_tag = false;
		char *equ = stream->vc.user_comments[i];
		while(equ-stream->vc.user_comments[i] < stream->vc.comment_lengths[i]) {
			if(*equ == '=') {
				has_tag = true;
				break;
			}
			equ++;
		}
		if(has_tag) {
			*equ = 0;
			comment->tag = strdup(stream->vc.user_comments[i]);
			*equ = '=';
			comment->comment = strdup(equ+1);
		} else {
			comment->comment = strdup(stream->vc.user_comments[i]);
		}
	}
	return first_comment;
}

int OGMReadStream::_getNextPage(ogg_page *page, WORD size) {
	int np, nread;
	char *buffer;
	while(1) {
		np = ogg_sync_pageseek(&oss, page);
		if(np>0) {
			break;
		} else if(np<0) {
			_RPT1(_CRT_WARN, "ogg_sync_pageseek failed, %d bytes skipped\n", -np);
		} else if(np==0) {
			buffer = ogg_sync_buffer(&oss, size);
			if(!buffer) {
				throw MyError("ogg_sync_buffer failed");
			}
			if((nread = parent->ReadData(buffer, size)) <= 0)
				break;
			ogg_sync_wrote(&oss, nread);
		}
	}
	return np;
}

ogg_int64_t OGMReadStream::samplesLag(__int64 lStart) {
	if( (lStart < 0) || (lStart >= length))
		return 0;
	if(can_cut_first_packet) {
		// For txt stream, if lStart!=0, return 1, so that
		// first Packet start @ granulepos 1
		// This make the text allways shown by OggDS (not allways
		// the case if granulepos = 0)
		if(lStart && (stream->type==S_TEXT))
			return 1;
		else
			return 0;
	}

	//Find our nearest pointer (first, current or last)
	if(lStart < currentPage->granulepos / 2)
		currentPage = firstData;
	else if(lStart > (lastPage->granulepos + currentPage->granulepos) / 2)
		currentPage = lastPage;

	if(currentPage->granulepos< lStart) {
		// Go forward
		while((currentPage != lastPage) && (currentPage->NextFromHead()->granulepos<lStart) )
			currentPage = currentPage->NextFromHead();
	} else if(currentPage->granulepos > lStart) {
		// Go backward
		while((currentPage != firstData) && (currentPage->granulepos>lStart) )
			currentPage = currentPage->NextFromTail();
	}

	// currentPage contains the Packet corresponding to lStart
	// if its granulepos equals lStart, we must verify if previous pages
	// do not contain this (possibly) spanned packet
	// the 64th bit of file_pos tell if this is a continued page
	if( (currentPage->granulepos==lStart) && (currentPage->file_pos>>63) ) {
		currentPage = currentPage->NextFromTail();
		while((currentPage->file_pos>>63) && (!currentPage->nb_packets))
			currentPage = currentPage->NextFromTail();
	}

	int packets_to_skip = 0;
	// Determines how many packets we must skip before reaching the wanted one
	ogg_int64_t gpos = currentPage->granulepos;
	// For the first sample, the packet may not be the first in the page
	// The problem : Page's granulepos = 0, but first Packet may be header or comment :(
	// Fortunately we prepared ourself for this case :)
	if(!lStart)
		packets_to_skip = first_packet;
	while( (gpos < lStart) && (packets_to_skip<currentPage->nb_packets) ) {
		gpos += currentPage->n_samples[packets_to_skip];
		packets_to_skip++;
	}

	//gpos = beginning of the Packet (that we would have read)
	// If lStart == gpos, there is no no lag
	// If not (lStart < gpos), we would have skipped gpos-lStart samples
	return gpos-lStart;
}
//////////////////////////////////////////////////////////////////////////////////
// Oh special case for audio stream : I consider every frame as a key-frame
// I will correct this if needed when I will start putting some writing routines
// for OGM in VDub ... (this may take a while)

OGMTextReadStream::OGMTextReadStream(OGMReadHandler *parent, streamNode *stream)
: OGMAudioReadStream(parent, stream) {
	can_cut_first_packet = true;
}

bool OGMTextReadStream::isEOS() {
	// Assume EOS reached when current page == last one
	return (currentPage==stream->pages.AtTail());
}

OGMAudioReadStream::OGMAudioReadStream(OGMReadHandler *parent, streamNode *stream)
: OGMReadStream(parent, stream) {
#ifdef __OGM_CAN_CUT_FIRST_PACKET__
	can_cut_first_packet = true;
#endif
}

OGMAudioReadStream::~OGMAudioReadStream() {
}

bool OGMAudioReadStream::IsKeyFrame(long lFrame) {
	if( (lFrame<0) || (lFrame>=length) )
		return false;

	return true;
}

long OGMAudioReadStream::PrevKeyFrame(long lFrame) {
	if(lFrame<1)
		return -1;

	if(lFrame>=length)
		return length-1;

	return lFrame-1;
}

long OGMAudioReadStream::NextKeyFrame(long lFrame) {
	if(lFrame>=length-1)
		return -1;

	if(lFrame<0)
		return 0;

	return lFrame+1;
}

long OGMAudioReadStream::NearestKeyFrame(long lFrame) {
	if(lFrame<0)
		return -1;

	if(lFrame>=length-1)
		return length-1;

	return lFrame;
}

bool OGMAudioReadStream::isKeyframeOnly() {
   return true;
}

//////////////////////////////////////////////////////////////////////////////////

OGMReadHandler::OGMReadHandler(char *s) {

	if (!g_disklockinited) {
		g_disklockinited=true;
		InitializeCriticalSection(&g_diskcs);
	}

	_construct(s);
}

OGMReadHandler::~OGMReadHandler() {
	_destruct();
}

void OGMReadHandler::AddRef() {
	++ref_count;
}

void OGMReadHandler::Release() {
	if(!--ref_count)
		delete this;
}

IAVIReadStream *OGMReadHandler::GetStream(DWORD fccType, LONG lParam) {
	streamNode *stream, *streamNext;
	stream = streams.AtHead();
	if(stream)
	while(streamNext = stream->NextFromHead()) {
		switch(stream->type) {
			case S_VIDEO:
				if( (fccType==streamtypeVIDEO) && (!lParam--) )
					return new OGMReadStream(this, stream);
				break;
			case S_AUDIO:
			case S_VORBIS:
				if( (fccType==streamtypeAUDIO) && (!lParam--) )
					return new OGMAudioReadStream(this, stream);
				break;
			case S_TEXT:
				if( (fccType==streamtypeTEXT) && (!lParam--) )
					return new OGMTextReadStream(this, stream);
				break;
		}
		stream = streamNext;
	}
	return NULL;
}

void OGMReadHandler::EnableFastIO(bool enab) {
	fDisableFastIO = !enab;
}

bool OGMReadHandler::isOptimizedForRealtime() {
	return false;
}

bool OGMReadHandler::isStreaming() {
	return false;
}

bool OGMReadHandler::isIndexFabricated() {
	return false;
}

bool OGMReadHandler::AppendFile(const char *pszFile) {
	return false;
}

bool OGMReadHandler::getSegmentHint(const char **ppszPath) {
	if (ppszPath)
		*ppszPath = NULL;
	return false;
}

void OGMReadHandler::_construct(char *pszFile) {
	hFile			= INVALID_HANDLE_VALUE;
	hFileUnbuffered = INVALID_HANDLE_VALUE;
	ref_count		= 1;
	currentStream = NULL;
	bytes_read = 0;
	bytes_skipped = 0;
	read_buffer = NULL;
	ogg_sync_init(&oss);
	num_pkt = 1;

	try {
		read_buffer = (char *)malloc(BLOCKSIZE);
		if(!read_buffer)
			throw MyMemoryError();

		// open file

		hFile = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);

		if (INVALID_HANDLE_VALUE == hFile)
			throw MyWin32Error("Couldn't open %s: %%s", GetLastError(), pszFile);

		i64FilePosition = 0;

		hFileUnbuffered = CreateFile(
				pszFile,
				GENERIC_READ,
				FILE_SHARE_READ,
				NULL,
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING,
				NULL
			);

		_parseFile();

	} catch(...) {
		_destruct();
		throw;
	}
}


void OGMReadHandler::_destruct() {
	while(currentStream = streams.RemoveTail())
		delete currentStream;

	if (hFile != INVALID_HANDLE_VALUE)
		CloseHandle(hFile);
	if (hFileUnbuffered != INVALID_HANDLE_VALUE)
		CloseHandle(hFileUnbuffered);

	if(read_buffer)
		free(read_buffer);
}

void OGMReadHandler::_addStream(ogg_page *page) {
	if(ogg_page_pageno(page)!=0)
		return;

	int sno = ogg_page_serialno(page);

	currentStream = new streamNode();
	streams.AddTail(currentStream);

	// This is a new stream, create its first Page now
	pageNode *lastPage = new pageNode();
	if(!lastPage)
		throw MyMemoryError();
	currentStream->pages.AddTail(lastPage);

	// Beginning => granulepos = 0;
	lastPage->granulepos = 0;
	lastPage->file_pos = bytes_read;
	if(ogg_page_continued(page))
		lastPage->file_pos |= ((unsigned __int64)0x1)<<63;
	lastPage->size = page->header_len + page->body_len;
	if((lastPage->nb_packets=ogg_page_packets(page))>0) {
		lastPage->n_samples = new long[lastPage->nb_packets];
		lastPage->n_bytes = new long[lastPage->nb_packets];
		if((!lastPage->n_samples) || (!lastPage->n_bytes))
			throw MyMemoryError();
		memset(lastPage->n_samples, 0, lastPage->nb_packets * sizeof(long));
		memset(lastPage->n_bytes, 0, lastPage->nb_packets * sizeof(long));
	}
	bytes_read += page->header_len + page->body_len;
	num_pkt = 1;

	currentStream->serial = ogg_page_serialno(page);
	currentStream->sstate = new ogg_stream_state;
	if(!currentStream->sstate)
		throw MyMemoryError();

	if(ogg_stream_init(currentStream->sstate, currentStream->serial))
		throw MyError("Could not initialize the ogg_stream_state");

	if(ogg_stream_pagein(currentStream->sstate, page))
		throw MyError("ogg_stream_pagein failed");

	ogg_packet packet;
	while(ogg_stream_packetout(currentStream->sstate, &packet)==1) {
		if(packet.packetno==0) {
			// This is the header packet
			if((packet.bytes >= 7) && !_strnicmp((const char *)&packet.packet[1], "vorbis", 6)) {
				// Vorbis stream
				currentStream->type = S_VORBIS;
				vorbis_info_init(&currentStream->vi);
				vorbis_comment_init(&currentStream->vc);
				currentStream->sh.size = sizeof(stream_header);
				currentStream->sh.time_unit = 10000000;
				if(vorbis_synthesis_headerin(&currentStream->vi, &currentStream->vc, &packet) < 0) {
					_RPT0(_CRT_WARN, "Invalid Vorbis stream\n");
					currentStream->type = S_OTHER;
					return;
				}
				currentStream->sh.samples_per_unit = currentStream->vi.rate;
				currentStream->sh.default_len = 1;
				currentStream->sh.buffersize = -1;
				currentStream->sh.bits_per_sample = currentStream->vi.bitrate_nominal / currentStream->vi.rate;
				currentStream->sh.sh.audio.channels = currentStream->vi.channels;
				currentStream->sh.sh.audio.blockalign = (currentStream->vi.bitrate_nominal * currentStream->vi.channels) / (8 * currentStream->vi.rate);
				currentStream->sh.sh.audio.avgbytespersec = currentStream->vi.bitrate_nominal / 8;
				currentStream->sample_rate = currentStream->vi.rate;
			} else if( (packet.bytes >= 142)
				&& !_strnicmp((const char *)&packet.packet[1],"Direct Show Samples embedded in Ogg", 35) ) {
					currentStream->type = S_OTHER;
					return;
			} else if( ((*packet.packet & PACKET_TYPE_BITS ) == PACKET_TYPE_HEADER)
				&& (packet.bytes >= (int)sizeof(stream_header) + 1) ) {

				memcpy(&currentStream->sh, &packet.packet[1], sizeof(stream_header));

				if(currentStream->sh.time_unit)
					currentStream->sample_rate = (double)10000000 * (double)currentStream->sh.samples_per_unit / (double)currentStream->sh.time_unit;
				// What type of stream ?
				if(!_strnicmp((const char *)&packet.packet[1], "audio", 5)) {
					currentStream->type = S_AUDIO;
				} else if(!_strnicmp((const char *)&packet.packet[1], "video", 5)) {
					currentStream->type = S_VIDEO;
				} else if(!_strnicmp((const char *)&packet.packet[1], "text", 4)) {
					currentStream->type = S_TEXT;
				} else {
					currentStream->type = S_OTHER;
					return;
				}
			} else {
				_RPT0(_CRT_WARN, "Invalid ogg header\n");
				currentStream->type = S_OTHER;
				return;
			}
			currentStream->num_headers++;
			if(currentStream->type == S_VORBIS) {
				currentStream->num_headers = 3;
				currentStream->has_comments = true;
			}
		}
		_addPacket(&packet);
	}
}

streamNode *OGMReadHandler::_getStream(int serial) {
	streamNode *stream, *streamNext;
	stream = streams.AtHead();
	if(stream)
	while(streamNext = stream->NextFromHead()) {
		if(stream->serial == serial)
			return stream;
		stream = streamNext;
	}
	return NULL;
}

void OGMReadHandler::_addPage(ogg_page *page) {
	if(ogg_page_pageno(page)==0) {
		_addStream(page);
		return;
	}

	currentStream = _getStream(ogg_page_serialno(page));
	if(!currentStream) {
		_RPT1(_CRT_WARN, "Stream %d not handled\n", ogg_page_serialno(page));
		bytes_read += page->header_len + page->body_len;
		return;
	}

	if(ogg_stream_pagein(currentStream->sstate, page))
		throw MyError("ogg_stream_pagein failed");

	// We will estimate the absolute granulepos of the first sample in the Page
	// For that we need the granulepos of the previous Page, and the number
	// of samples in this previous Page
	// Nb : here granulepos starts @ 0 (convenient way since VirtualDub acts the same)
	ogg_int64_t granulepos = currentStream->pages.AtTail()->granulepos;
	for(int i=0 ; i<currentStream->pages.AtTail()->nb_packets ; i++)
		granulepos += currentStream->pages.AtTail()->n_samples[i];

	pageNode *lastPage = new pageNode();
	if(!lastPage)
		throw MyMemoryError();
	currentStream->pages.AddTail(lastPage);

	lastPage->granulepos = granulepos;
	lastPage->file_pos = bytes_read;
	lastPage->size = page->header_len + page->body_len;
	if(ogg_page_continued(page))
		lastPage->file_pos |= ((unsigned __int64)0x1)<<63;
	if((lastPage->nb_packets=ogg_page_packets(page))>0) {
		lastPage->n_samples = new long[lastPage->nb_packets];
		lastPage->n_bytes = new long[lastPage->nb_packets];
		if((!lastPage->n_samples) || (!lastPage->n_bytes))
			throw MyMemoryError();
		memset(lastPage->n_samples, 0, lastPage->nb_packets * sizeof(long));
		memset(lastPage->n_bytes, 0, lastPage->nb_packets * sizeof(long));
	}
	bytes_read += page->header_len + page->body_len;
	num_pkt = 1;

	ogg_packet packet;
	while(ogg_stream_packetout(currentStream->sstate, &packet)==1) {
		_addPacket(&packet);
	}
}

void OGMReadHandler::_addPacket(ogg_packet *packet) {
	pageNode *lastPage = currentStream->pages.AtTail();

	if(!lastPage)
		throw MyError("OGMReadHandler::_addPacket : no Page created yet!");

	if(num_pkt>lastPage->nb_packets)
		throw MyError("Invalid Page");

	// Treat the Packet
	if(packet->bytes<=0)
		return;

	if(currentStream->type == S_VORBIS) {
		// Vorbis Packet
		if(packet->packetno<3)  {
			lastPage->n_bytes[num_pkt-1] = packet->bytes;
			// First packet already processed here
			if( (packet->packetno!=0) && (vorbis_synthesis_headerin(&currentStream->vi, &currentStream->vc, packet)<0)) {
				_RPT1(_CRT_WARN, "Stream %d is not a valid Vorbis stream\n", currentStream->serial);
				currentStream->type = S_OTHER;
				return;
			}
		} else {
			int thisW = vorbis_packet_blocksize(&currentStream->vi, packet);
			// Found in vcut (part of vorbis-tools : see www.vorbis.org)
			int n_samples = (thisW+currentStream->prevW)/4;
			lastPage->n_samples[num_pkt-1] = n_samples;
			lastPage->n_bytes[num_pkt-1] = packet->bytes;
			currentStream->n_samples += n_samples;
			currentStream->length++;
			currentStream->prevW = thisW;
		}
	} else if( (*packet->packet & 0x01) == PACKET_TYPE_DATA) {
		// Data packet
		int hdrlen, i;
		long lenbytes = 0;
		hdrlen = (*packet->packet & PACKET_LEN_BITS01) >> 6;
		hdrlen |= (*packet->packet & PACKET_LEN_BITS2) << 1;
		for (i = 0; i < hdrlen; i++) {
			lenbytes = lenbytes << 8;
			lenbytes += *((unsigned char *)packet->packet + hdrlen - i);
		}
		if(lenbytes == 0)
			lenbytes = 1;
		lastPage->n_samples[num_pkt-1] = lenbytes;
		lastPage->n_bytes[num_pkt-1] = packet->bytes-1-hdrlen;
		currentStream->n_samples += lenbytes;
		currentStream->length++;
		// If this is a keyframe, add it to the list
		if( (currentStream->type == S_VIDEO) && (*packet->packet & PACKET_IS_SYNCPOINT)) {
			// For the moment only video streams can have delta frames
			// Other streams (audio / text) are keyframe only
			keyNode *lastKey = new keyNode();
			currentStream->keys.AddTail(lastKey);
			lastKey->packetno = num_pkt-1;
			lastKey->page = lastPage;
			lastKey->granulepos = lastPage->granulepos;
			for(i=0 ; i<num_pkt-1 ; i++)
				lastKey->granulepos += lastPage->n_samples[i];
		}
	} else if( (*packet->packet & PACKET_TYPE_BITS) == PACKET_TYPE_COMMENT) {
		// Comment Packet, but not a Vorbis stream
		lastPage->n_bytes[num_pkt-1] = packet->bytes;
		currentStream->num_headers++;
		currentStream->vi.channels = 2;
		currentStream->vi.rate = 48000;
		if(vorbis_synthesis_headerin(&currentStream->vi, &currentStream->vc, packet)<0) {
			_RPT1(_CRT_WARN, "Stream %d doesn't contain a valid Comment Packet\n", currentStream->serial);
		} else {
			currentStream->has_comments = true;
		}
	} else {
		// Probably a Header Packet, or a corrupted one
		lastPage->n_bytes[num_pkt-1] = packet->bytes;
	}
	if(lastPage->n_bytes[num_pkt-1] > currentStream->max_packet_size)
		currentStream->max_packet_size = lastPage->n_bytes[num_pkt-1];
	num_pkt++;
}

void OGMReadHandler::_parseFile() {
	ogg_page       page;

	__int64 filesize = _sizeFile();

	long page_limit = filesize / (1000 * 4096);
	if(page_limit <= 0)
		page_limit = 1;

	ProgressDialog pd(NULL, "Ogg Media File Import Filter", "Parsing Ogg Media File", (long)(filesize/1024), true);

	pd.setValueFormat("%ldK of %ldK");

	ogg_sync_init(&oss);
	long pages = 0;
	while(_getNextPage(&page)>0) {
		_addPage(&page);
		if( (pages % page_limit) == 0) {
			pd.advance((long)(bytes_read/1024));
			pd.check();
		}
		pages++;
	}
	ogg_sync_clear(&oss);
}

int OGMReadHandler::_getNextPage(ogg_page *page) {
	int np, nread;
	char *buffer;
	while(1) {
		np = ogg_sync_pageseek(&oss, page);
		if(np>0) {
			break;
		} else if(np<0) {
			_RPT1(_CRT_WARN, "ogg_sync_pageseek failed, %d bytes skipped\n", -np);
			bytes_read += (-np);
			bytes_skipped += (-np);
		} else if(np==0) {
			buffer = ogg_sync_buffer(&oss, BLOCKSIZE);
			if(!buffer) {
				throw MyError("ogg_sync_buffer failed");
			}
			if((nread = _readFile(buffer, BLOCKSIZE)) <= 0)
				break;
			ogg_sync_wrote(&oss, nread);
		}
	}
	return np;
}

long OGMReadHandler::ReadData(void *buffer, long nBytes) {
	return _readFile(buffer, nBytes);
}

void OGMReadHandler::SeekFile(__int64 filePos) {
	//if(_posFile() != filePos)
		_seekFile(filePos);
}