/* 
 *	Copyright (C) Chia-chen Kuo - April 2001
 *
 *  This file is part of DVD2AVI, a free MPEG-2 decoder
 *	
 *  DVD2AVI 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, or (at your option)
 *  any later version.
 *   
 *  DVD2AVI 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

// 03/30/2002 transport stream support added - PCDVDGUY
// 03/30/2002 buffer size increase as protection against crash - trbarry

#include "global.h"
#include "getbit.h"
#include "gethdr.h"
#include "store.h"
#include "alloc.h"
#include "getpic.h"
#include "conf.h"

static int ChromaFormat[4] = {
	0, 6, 8, 12
};

static void GOPBack(void);
static void InitialDecoder(void);
static int MPEG2Dec_Begin(void);

DWORD WINAPI MPEG2Dec(LPVOID n)
{
	DVD_T("MPEG2Dec");
	UINT streaming = (UINT)n;
	// 0 to begin decoding
	// 1 for the next frame
	// X>0 to go X frames forward

	//if (streaming < 3)
	static int header_or_not_header = 0;
	if (g_Flags.InitDecoder)
	{
		if (MPEG2Dec_Begin())
			return 0;
		header_or_not_header = Get_Hdr();
	}
	else
		header_or_not_header = 1;
	while (header_or_not_header && ((streaming > g_PicInfo.Frame_Number) || g_Flags.D2V_Flag /*|| g_Flags.Play_Flag*/))
	{
		if (Decode_Picture()) return 0;

		if (g_Flags.Stop_Flag)
		{
			g_Flags.Fault_Flag = 99;
			Write_Frame(NULL, g_DVDGlobals.d2v_current, 0);
		}
		header_or_not_header = Get_Hdr();
	}

	return 0;
}

static void GOPBack(void)
{
	int startfile;
	__int64 startloc, endloc;

	startfile = process.startfile;
	startloc = process.startloc;

	for (;;)
	{
		endloc = startloc + BUFFER_SIZE;
		startloc -= BUFFER_SIZE<<4;

		if (startloc > 0)
		{
			process.startfile = startfile;
			process.startloc = startloc;
		}
		else
		{
			process.startloc = 0;
			return;
		}

		_lseeki64(g_DVDGlobals.Infile[process.startfile], process.startloc, SEEK_SET);
		Initialize_Buffer();

		while (Get_Hdr() && _telli64(g_DVDGlobals.Infile[process.startfile]) <= endloc)
		{
			if (g_DVDGlobals.picture_coding_type==I_TYPE)
			{
				process.startfile = g_DVDGlobals.d2v_current.file;
				process.startloc = g_DVDGlobals.d2v_current.lba * BUFFER_SIZE;
				return;
			}
		}
	}
}

static void InitialDecoder(void)
{
	DVD_T("InitialDecoder");
	int i, size;

	g_PicInfo.mb_width = (g_PicInfo.horizontal_size+15)/16;
	g_PicInfo.mb_height = g_DVDGlobals.progressive_sequence ? (g_PicInfo.vertical_size+15)/16 : 2*((g_PicInfo.vertical_size+31)/32);

	g_PicInfo.Coded_Picture_Width = 16 * g_PicInfo.mb_width;
	g_PicInfo.Coded_Picture_Height = 16 * g_PicInfo.mb_height;

	g_PicInfo.Chroma_Width = (g_DVDGlobals.chroma_format==CHROMA444) ? g_PicInfo.Coded_Picture_Width : g_PicInfo.Coded_Picture_Width>>1;
	g_PicInfo.Chroma_Height = (g_DVDGlobals.chroma_format!=CHROMA420) ? g_PicInfo.Coded_Picture_Height : g_PicInfo.Coded_Picture_Height>>1;

	g_PicInfo.block_count = ChromaFormat[g_DVDGlobals.chroma_format];

	for (i=0; i<3; i++)
	{
		if (i==0)
			size = g_PicInfo.Coded_Picture_Width * (g_PicInfo.Coded_Picture_Height+1);
		else
			size = g_PicInfo.Chroma_Width * (g_PicInfo.Chroma_Height+1);
	//	(g_PicInfo.Coded_Picture_Height+1) : +1 for YV12 bicubic filter
		// Sloppy fix doubles the size of following 3 buffers to protect against
		// garbage data falling off the end. - trbarry 3/2002
		g_PicInfo.backward_reference_frame[i] = (unsigned char*)aligned_malloc(2*size,128);
		g_PicInfo.forward_reference_frame[i] = (unsigned char*)aligned_malloc(2*size,128);
		g_PicInfo.auxframe[i] = (unsigned char*)aligned_malloc(2*size,128);
	}

	g_PicInfo.u422 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height/2,128);
	g_PicInfo.v422 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height/2,128);
	g_PicInfo.u444 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height,128);
	g_PicInfo.v444 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height,128);
	g_PicInfo.rgb24 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height*3,128);
	g_PicInfo.rgb24_temp = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height*3,128);
	g_PicInfo.rgb24_temp2 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height*3,128);
	g_PicInfo.yuy2 = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height*2,128);
	g_PicInfo.yuy2_temp = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height*2,128);

//	g_PicInfo.lum = (unsigned char*)aligned_malloc(g_PicInfo.Coded_Picture_Width*g_PicInfo.Coded_Picture_Height,128);

}


static int MPEG2Dec_Begin(void)
{
	int i;
	unsigned char temp_buffer[ BUFFER_SIZE>>1 ];

	g_Flags.Pause_Flag = g_Flags.Stop_Flag = g_Flags.Rip_Flag = g_Flags.Fault_Flag = 0;
	g_PicInfo.Frame_Number = g_PicInfo.Second_Field = 0;
	g_PicInfo.VOB_ID = g_PicInfo.CELL_ID = 0;
	g_DVDGlobals.Sound_Max = 1; g_DVDGlobals.Bitrate_Meter = 0;

	for (i=0; i<CHANNEL; i++)
	{
		ZeroMemory(&g_DVDGlobals.mpa[i], sizeof(MPAStream));	
		ZeroMemory(&g_DVDGlobals.ac3[i], sizeof(AC3Stream));
	}

	ZeroMemory(&pcm, sizeof(PCMStream));
	ZeroMemory(&pcm2, sizeof(PCMStream));
	ZeroMemory(&g_DVDGlobals.CH, sizeof(g_DVDGlobals.CH));

	switch (process.locate)
	{
		case LOCATE_FORWARD:
			process.startfile = process.file;
			process.startloc = (process.lba + 2) * BUFFER_SIZE;

			process.end = process.total - BUFFER_SIZE;
			process.endfile = g_Flags.File_Limit - 1;
			process.endloc = (process.length[g_Flags.File_Limit-1]/BUFFER_SIZE - 1) * BUFFER_SIZE;
			break;

		case LOCATE_BACKWARD:
			process.startfile = process.file;
			process.startloc = (process.lba - 2) * BUFFER_SIZE;

			process.end = process.total - BUFFER_SIZE;
			process.endfile = g_Flags.File_Limit - 1;
			process.endloc = (process.length[g_Flags.File_Limit-1]/BUFFER_SIZE - 1)*BUFFER_SIZE;

			GOPBack();
			break;

		case LOCATE_RIP:
			process.startfile = process.leftfile;
			process.startloc = process.leftlba * BUFFER_SIZE;
			process.endfile = process.rightfile;
			process.endloc = (process.rightlba - 1) * BUFFER_SIZE;

			process.run = 0;
			for (i=0; i<process.startfile; i++)
				process.run += process.length[i];
			process.start = process.run + process.startloc;

			process.end = 0;
			for (i=0; i<process.endfile; i++)
				process.end += process.length[i];
			process.end += process.endloc;
			break;
	}



	// search MPEG-2 Sequence Header
	if (!g_Flags.Check_Flag)
	{
		int code;

		g_Flags.File_Flag = 0;
		_lseeki64(g_DVDGlobals.Infile[0], 0, SEEK_SET);
		Initialize_Buffer();

		code = strlen(g_DVDGlobals.Infilename[g_Flags.File_Flag]); // length of filename

		//////////////////////////////////////////////////////////////////
		//
		// Analyze the current input file, we need to hand-inspect
		// the data to identify MPEG-2 transport streams.
		//
		// 1) if the filename has a certain extension, we'll assume
		//    it's a transport stream
		// 2) otherwise, we'll check the first few thousand bytes, and 
		//    see if contains a 3-consecutively placed transport
		//    sync-bytes

		// does file-extension end with ".trp"?
		if ( strcmpi( ".trp", (g_DVDGlobals.Infilename[g_Flags.File_Flag] + code - 4)) == 0 ||
			   strcmpi( ".trp\"", (g_DVDGlobals.Infilename[g_Flags.File_Flag] + code - 5)) == 0 )
			g_Flags.SystemStream_Flag = 2; // yes, UNCONDITIONALLY set flag

		// does file-extension end with ".ts"?
		if ( strcmpi( ".ts", (g_DVDGlobals.Infilename[g_Flags.File_Flag] + code - 3)) == 0 ||
			   strcmpi( ".ts\"", (g_DVDGlobals.Infilename[g_Flags.File_Flag] + code - 4)) == 0 )
			g_Flags.SystemStream_Flag = 2; // yes, UNCONDITIONALLY set flag

		// now manually search through the start of the file, for MPEG-2
		// transport startcodes
		for ( i = 0; i+1 < (BUFFER_SIZE>>1); i = i + 1 )
			temp_buffer[ i ] = Get_Byte();  // fill our temp_buffer[]

		for ( i = 0; (i+(3*188)) < (BUFFER_SIZE>>1); i = i + 1 )
		{
			// search for *3* consecutive MPEG-2 transport bytes
			if ( temp_buffer[ i ] == 0x47 && temp_buffer[ i + 188 ] == 0x47
				   && temp_buffer[ i + 2*188] == 0x47 ) 
				g_Flags.SystemStream_Flag = 2;
		}

		// reset the file-pointer!
		g_Flags.File_Flag = 0;
		_lseeki64(g_DVDGlobals.Infile[0], 0, SEEK_SET);
		Initialize_Buffer();

		g_Flags.File_Flag = 0;
		_lseeki64(g_DVDGlobals.Infile[0], 0, SEEK_SET);
		Initialize_Buffer();

		while (!g_Flags.Check_Flag)
		{
			next_start_code();
			code = Get_Bits(32);

			switch (code)
			{
				case PACK_START_CODE:
					g_Flags.SystemStream_Flag = 1;
					break;

				case SEQUENCE_HEADER_CODE:
					sequence_header();
					InitialDecoder();
					g_Flags.Check_Flag = 1;
					break;
			}
		}
	}

	g_DVDGlobals.Frame_Rate = (g_Flags.FO_Flag==FO_FILM) ? g_DVDGlobals.frame_rate * 0.8f : g_DVDGlobals.frame_rate;

	if (g_Flags.D2V_Flag)
	{
		i = g_Flags.File_Limit;

		g_OutFunctions.D2VOutput( "DVD2AVIProjectFile\n%d", i);
		while (i)
		{
			g_OutFunctions.D2VOutput( "\n%d ", strlen(g_DVDGlobals.Infilename[g_Flags.File_Limit-i]));
			g_OutFunctions.D2VOutputString( g_DVDGlobals.Infilename[g_Flags.File_Limit-i]);
			i--;
		}

		g_OutFunctions.D2VOutput( "\n\nStream_Type=%d,0,0\n", g_Flags.SystemStream_Flag);

		// if this is a MPEG-2 transport-stream, grab the Audio/Video PIDs here
		if ( g_Flags.SystemStream_Flag == 2 )
		{
			g_OutFunctions.D2VOutput( "MPEG2_Transport_PID=%X", g_Flags.MPEG2_Transport_VideoPID);
			g_OutFunctions.D2VOutput(",%X\n",g_Flags.MPEG2_Transport_AudioPID);
		}
		g_OutFunctions.D2VOutput( "iDCT_Algorithm=%d\n", g_Flags.iDCT_Flag);
		g_OutFunctions.D2VOutput( "YUVRGB_Scale=%d\n", g_Flags.Scale_Flag);
		g_OutFunctions.D2VOutput("Luminance=%d", g_PicInfo.LumGain);
		g_OutFunctions.D2VOutput(",%d\n", g_PicInfo.LumOffset);
		g_OutFunctions.D2VOutput( "Picture_Size=%d", g_PicInfo.Clip_Top);
		g_OutFunctions.D2VOutput(",%d",g_PicInfo.Clip_Bottom);
		g_OutFunctions.D2VOutput(",%d",g_PicInfo.Clip_Left);
		g_OutFunctions.D2VOutput(",%d",g_PicInfo.Clip_Right);
		g_OutFunctions.D2VOutput(",%d",g_PicInfo.Squeeze_Width);
		g_OutFunctions.D2VOutput(",%d\n",g_PicInfo.Squeeze_Height);
		g_OutFunctions.D2VOutput( "Field_Operation=%d\n", g_Flags.FO_Flag);
		g_OutFunctions.D2VOutput( "Frame_Rate=%d\n", (int)(g_DVDGlobals.Frame_Rate*1000));
		g_OutFunctions.D2VOutput( "Aspect_Change=%d\n", g_PicInfo.Aspect_Change);
		g_OutFunctions.D2VOutput( "FilterType=%d\n", (int)(g_PicInfo.Resize_Function));

		g_OutFunctions.D2VOutput( "Location=%d", process.leftfile);
		g_OutFunctions.D2VOutput( ",%X",(int)process.leftlba);
		g_OutFunctions.D2VOutput(",%d",process.rightfile);
		g_OutFunctions.D2VOutput( ",%X\n",(int)process.rightlba);
	}

	g_Flags.File_Flag = process.startfile;
	_lseeki64(g_DVDGlobals.Infile[process.startfile], (process.startloc/BUFFER_SIZE)*BUFFER_SIZE, SEEK_SET);
	Initialize_Buffer();

	process.op = 0;

	while (Get_Hdr() && g_DVDGlobals.picture_coding_type!=I_TYPE);

	g_Flags.Rip_Flag = 1;
	process.file = g_DVDGlobals.d2v_current.file;
	process.lba = g_DVDGlobals.d2v_current.lba;

	if (Decode_Picture()) return 1;

	while (Get_Hdr() && g_DVDGlobals.picture_coding_type==B_TYPE);

	if (Decode_Picture()) return 1;

	process.op = process.mi = timeGetTime();
	return 0;
}

