/***********************************************************************
 * 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
 *
 ***********************************************************************
 *
 *
 *
 */

#include "../VirtualDub.h"

#include <string.h>
#include <crtdbg.h>

#include <windows.h>

#include "../gui.h"
#include "../crash.h"

#include "../Error.h"
#include "../AudioSource.h"

#include "OGMAudio.h"


#define __OGM_AUDIO_OVERLAP__


long AudioTranslateVideoSubset2(FrameSubset& dst, FrameSubset& src, long usPerFrame, WAVEFORMATEX *pwfex) {
	FrameSubsetNode *fsn = src.getFirstFrame();
	const __int64 i64usPerFrame = usPerFrame;
	const long lSampPerSec = pwfex->nSamplesPerSec;
	const long nBytesPerSec = pwfex->nAvgBytesPerSec;
	const int nBlockAlign = pwfex->nBlockAlign;
	long total = 0;

	// I like accuracy, so let's strive for accuracy.  Accumulate errors as we go;
	// use them to offset the starting points of subsequent segments, never being
	// more than 1/2 segment off.
	//
	// The conversion equation is in units of (1000000*nBlockAlign).

	// nAvgBytesPerSec / nBlockAlign = nSamplesPerSec

	__int64 nError = 0;
	__int64 nMultiplier = i64usPerFrame * lSampPerSec;
	__int64 nDivisor = 1000000i64;
	__int64 nRound = nDivisor/2;
	long nTotalFramesAccumulated = 0;

	while(fsn) {
		long start, end;

		// Compute error.
		//
		// Ideally, we want the audio and video streams to be of the exact length.
		//
		// Audiolen = (videolen * usPerFrame * nBytesPerSec) / (1000000*nBlockAlign);
		// nBytesPerSec / nBlockAlign = nSamplesPerSec

		nError = total*nDivisor - (nTotalFramesAccumulated * nMultiplier);

_RPT1(0,"nError = %I64d\n", nError);

		// Add a block.

		start = ((__int64)fsn->start * nMultiplier + nRound + nError) / nDivisor;
		end = ((__int64)(fsn->start + fsn->len) * nMultiplier + nRound) / nDivisor;

		nTotalFramesAccumulated += fsn->len;

		dst.addRange(start, end-start, false);

		total += end-start;

		fsn = src.getNextFrame(fsn);
	}

	return total;
}

OGMAudioSubset::OGMAudioSubset(AudioStream *src, FrameSubset *pfs, long usPerFrame, long preskew, BOOL is_main_audio) : AudioSubset() {
	memcpy(AllocFormat(src->GetFormatLen()), src->GetFormat(), src->GetFormatLen());

	this->is_main_audio = is_main_audio;

	SetSource(src);

	long total = AudioTranslateVideoSubset2(subset, *pfs, usPerFrame, src->GetFormat());

	subset.deleteRange(0, MulDiv(preskew, src->GetFormat()->nSamplesPerSec, 1000));

	stream_len = total;

	SetLimit(total);

	pfsnCur = subset.getFirstFrame();
	iOffset = 0;
	lSrcPos = 0;
	// Arrgh we may treat VBR streams here, so lSkipSize is not really accurate
	// I will divide this value by 3 (i.e. I assume the stream won't exceed
	// 3*average_bitrate as instant bitrate in a particular time)
	// Well anyway normally this value will be of no use when treating OGM
	// but just in case ...
	lSkipSize = (sizeof skipBuffer * src->GetFormat()->nSamplesPerSec) / src->GetFormat()->nAvgBytesPerSec;
	lSkipSize /= 3;
	if(lSkipSize <= 0)
		lSkipSize = 1;
}

long OGMAudioSubset::_Read(void *buffer, long samples, long *lplBytes) {
	long offset, actual;

	if (!pfsnCur) {
		*lplBytes = 0;
		return 0;
	}

	/* Modification for OGM
	 *
	 * VirtualDub native routines read exactly "samples" samples in the
	 * stream, no more no less. But for OGM we read as many samples as
	 * we can in a frame (AC3 or MP3 stream) or Packet (Ogg/Ogm stream).
	 *
	 * That's why I added a boolean in AudioStreamSource::Read to know
	 * if we have to test the number of samples read.
	 *
	 * But here this is another thing : the user deleted parts of the video
	 * (and so audio&text too). Of course we want all the stuffs still
	 * synched between each parts. With original routines, there is no
	 * problem since we read exactly "samples" samples, but it is not the case
	 * for OGM :( : depending how many samples we will read at a time coming
	 * from the input stream, we may end up with a lag of 0 (ideal case indeed)
	 * up to a certain amount of samples (example : one MPeg3-Layer1 frame
	 * contains 1152 samples, an AC3 contains 1536 samples, and in general
	 * onr Packet of Vorbis stream contains 1024 samples ... but in an OGM
	 * file the orignal stream may have cut the MP3/AC3 frames into various
	 * Packets, or even merged various frames into one Packet ....)
	 *
	 * To keep synchronisation, I thought of two possibilities here :
	 * 1. When we reach the end of a part and that the number of samples
	 *    read exceed the end of this part, assume we in fact read
	 *    enough samples to just reach the part (so we lower the number
	 *    of samples really read)
	 * 2. Keep in my mind by how many samples we exeeded the end of the part
	 *    , reporting this number to the beginning of the next part (thanks
	 *    to the iOffset variable gracefully provided by Avery Lee :) )
	 *
	 * In any case we must take into account the type of the stream (because
	 * here we may treat audio stream, but also text stream).
	 * And for a text stream, only rule 1. is acceptable (unless you don't
	 * care seeing a subtitle continuing on a next part and thus replacing
	 * the subtitle you should see as beginning of this next part ;) ).
	 */
#ifdef __OGM_AUDIO_OVERLAP__
	while((offset = pfsnCur->start - lSrcPos) > 0) {
#else
	while((offset = (pfsnCur->start - lSrcPos) + iOffset) > 0) {
#endif
		long t;

		if(source->Skip(offset)) {
			lSrcPos += offset;
			break;
		}

		if(offset > lSkipSize) offset = lSkipSize;

		actual = source->Read(skipBuffer, offset, &t, FALSE);
		
		if(!actual) {
			*lplBytes = 0;
			return 0;
		}

		lSrcPos += actual;
	}

	if(samples > pfsnCur->len - iOffset)
		samples = pfsnCur->len - iOffset;

	samples = source->Read(buffer, samples, lplBytes, FALSE);

	// Update state of lSrcPos now : even if we state as having read less
	// samples than in reality, lSrcPos must reflect the state of the input
	// stream
	lSrcPos += samples;

#ifdef __OGM_AUDIO_OVERLAP__
	// All streams treated the same way
	// At the end of a part, iOffset will return to 0
	if(samples > pfsnCur->len - iOffset)
		samples = pfsnCur->len - iOffset;
#else
	// Treat text streams separately
	// Nb :
	//  - the main audio stream cannot be a text one
	//  - the main audio stream can be anything (audio in AVI, WAV or OGM)
	//    and is so based on AudioStream
	//  - other streams (other audio&text streams coming from OGM file or
	//    extern files) are all based on OGMAudioSource, which tell what kind
	//    of stream it is
	//
	//    So we can only assume the source is based on OGMAudioSource for
	//    all streams except the main audio (which fortunately cannot be
	//    a text one as I said) :
	if(!is_main_audio && ((OGMAudioSource *)GetAudioSource())->isText && (samples > pfsnCur->len - iOffset) )
		samples = pfsnCur->len - iOffset;
#endif

	// Update iOffset accordingly to the method we used
	iOffset += samples;

	while (pfsnCur && iOffset >= pfsnCur->len) {
		iOffset -= pfsnCur->len;
		pfsnCur = subset.getNextFrame(pfsnCur);
	}

	return samples;
}