
#include <windows.h>
#include <vfw.h>	// video for windows
#include <stdio.h>
#include "PajaTypes.h"
#include "AVIPlayerC.h"


using namespace PajaTypes;


/*
static
void
TRACE( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}


void WriteTGA( const char* szName, int iWidth, int iHeight, unsigned char* pData )
{
	FILE*			pStream;
	unsigned char	ucHeader[18];
	int				i, j;

	memset( ucHeader, 0, 18 );

	ucHeader[2] = 2;
	ucHeader[16] = 0x18;
	ucHeader[17] = 0x20;

	ucHeader[12] = iWidth;
	ucHeader[13] = iWidth >> 8;
	ucHeader[14] = iHeight;
	ucHeader[15] = iHeight >> 8;

	if( (pStream = fopen( szName, "wb" )) == 0 ) {
		return;
	}

	fwrite( ucHeader, 1, 18, pStream );

	for( i = 0; i < iHeight; i++ ) {
		for( j = 0; j < iWidth; j++ ) {
			fputc( pData[(j + i * iWidth) * 3], pStream );
			fputc( pData[(j + i * iWidth) * 3 + 1], pStream );
			fputc( pData[(j + i * iWidth) * 3 + 2], pStream );
		}
	}
	
	fclose( pStream );
}
*/


AVIPlayerC::AVIPlayerC() :
	m_pData( 0 ),
	m_pFrameBuffer( 0 ),
	m_hIC( 0 ),
	m_i32CurFrame( -1 )
{
	m_pFrameIndices = 0;
	m_pStreamFormat = 0;
	m_pTargetFormat = 0;
}

AVIPlayerC::~AVIPlayerC()
{
	delete m_pFrameIndices;
	delete m_pStreamFormat;
	delete m_pTargetFormat;
	delete m_pFrameBuffer;

	if( m_hIC ) {
		ICDecompressEnd( m_hIC );
		ICClose( m_hIC );
	}
}


void
AVIPlayerC::init_read( uint8* pData, uint32 ui32DataSize )
{
	m_pData = pData;
	m_ui32DataSize = ui32DataSize;
	m_i32DataPointer = 0;
}

bool
AVIPlayerC::read( void* pBuffer, int32 i32Size )
{
	if( (m_i32DataPointer + i32Size) > (int32)m_ui32DataSize ) {
		i32Size = (int32)m_ui32DataSize - m_i32DataPointer;
		memcpy( pBuffer, &m_pData[m_i32DataPointer], i32Size );
		m_i32DataPointer += i32Size;
		return false;
	}

	memcpy( pBuffer, &m_pData[m_i32DataPointer], i32Size );
	m_i32DataPointer += i32Size;

	return true;
}

void
AVIPlayerC::seek( int32 i32Offset, DataSeekE eSeek )
{
	switch( eSeek ) {
	case DATA_SEEK_SET:
		if( i32Offset >= 0 && i32Offset < (int32)m_ui32DataSize )
			m_i32DataPointer = i32Offset;
		else
			m_i32DataPointer = 0;
		break;
	case DATA_SEEK_END:
		if( i32Offset <= 0 && i32Offset > -(int32)m_ui32DataSize )
			m_i32DataPointer = (int32)m_ui32DataSize + i32Offset;
		else
			m_i32DataPointer = (int32)m_ui32DataSize;
		break;
	case DATA_SEEK_CUR:
		if( (m_i32DataPointer + i32Offset) >= 0 && (m_i32DataPointer + i32Offset) < (int32)m_ui32DataSize )
			m_i32DataPointer += i32Offset;
		break;
	}
}

int32
AVIPlayerC::get_pos()
{
	return m_i32DataPointer;
}


bool
AVIPlayerC::read_chunk_head( uint32* pID, uint32* pSize )
{
    if( !read( pID, sizeof( uint32 ) ) ) return false;
    if( !read( pSize, sizeof( uint32 ) ) ) return false;
    return true;
}


bool
AVIPlayerC::handle_hdrl( AVIMainHeaderS* pMainHdr, AVIStreamHeaderS* pStreamHdr, BITMAPINFOHEADER** pStreamFormat,
						int chunksize, bool& bHasVideo )
{
    int		filepos;
    int		i = 0;
    uint32	cs, tag;
    
    filepos = get_pos();
    
    while( i < chunksize ) {

        read_chunk_head( &tag, &cs );

//		AP_FOURCC2Str( tag, st );
//		TRACE( "Tag %s   size %u bytes.\n", st, chunksize );

        switch( tag ) {
		case AVIH_TAG:
			// Main AVI Header
			read( pMainHdr, sizeof( AVIMainHeaderS ) );
			break;

		case LIST_TAG:
			read( &tag, sizeof( uint32 ) );

//			AP_FOURCC2Str( tag, st );
//			TRACE( "  Tag %s\n", st );

			switch( tag ) {
			case STRL_TAG:
				{
					uint32 j,k, fcc;
					uint32 l = 0;
					bool	bIsVideo = false;

					while( l < cs ) {
						read_chunk_head( &j, &k );

//						AP_FOURCC2Str( j, st );
//						TRACE( "    Tag %s  size: %d\n", st, k );

						l += k;
						switch( j ) {
						case STRH_TAG:
							// Stream header
							read( &fcc, 4 );
							seek( -4, DATA_SEEK_CUR );
							switch( fcc ) {
							case VIDS_TAG: 
								bIsVideo = true;
								bHasVideo = true;
								read( pStreamHdr, k ); // + 4 );
								break;
							default:
								bIsVideo = false;
								seek( k, DATA_SEEK_CUR );
								break;
							}
							break;
						case STRF_TAG:
							// Stream format
//							TRACE( "format size: %d (%s)\n", k, bIsVideo ? "video" : "novideo" );
							if( bIsVideo ) {
								*pStreamFormat = (BITMAPINFOHEADER*) new uint8[k];
//								seek( -8, DATA_SEEK_CUR );
								read( *pStreamFormat, k );
								bIsVideo = false;
							} else
								seek( k, DATA_SEEK_CUR );
							break;

						default:
							seek( k, DATA_SEEK_CUR );
						}
					}
				}
				break;
			}
		}
		i += 8 + cs;
		seek( filepos + i, DATA_SEEK_SET );
    }

    return true;
}


bool
AVIPlayerC::parse_file( uint8* pData, uint32 ui32DataSize )
{
	char		st[5];

	uint32		ui32Tag;
	uint32		ui32ChunkSize;
	uint32		ui32FilePos;
	uint32		ui32MoviPos;
	uint32		ui32HdrlPos;
	uint32		ui32IdxPos;
	bool		bDoneMovi = false;
	bool		bDoneHdrl = false;
	bool		bIdxDone = false;
	bool		bHasVideo = false;
	uint32		ui32FrameCount = 0;	

	// int read stuff
	init_read( pData, ui32DataSize );


	
//	TRACE( "Contents of file (%i bytes):\n\n", ui32DataSize );
	
	read_chunk_head( &ui32Tag, &ui32ChunkSize );
	
	if( ui32Tag != RIFF_TAG ) {
		AP_FOURCC2Str( ui32Tag, st );
//		TRACE( "Not a RIFF file (%s).\n", st );
		return false;
	}
	
	read( &ui32Tag, sizeof( uint32 ) );
//	AP_FOURCC2Str( tag, st );
//	TRACE( "Riff of type %s   size %u bytes.\n", st, chunksize );
	
	ui32FilePos = 12;
	
	m_pStreamFormat = 0;
	m_pTargetFormat = 0;
	m_pFrameIndices = 0;
	
	for( ; ui32FilePos < ui32DataSize; ) {
		
		read_chunk_head( &ui32Tag, &ui32ChunkSize );

//		AP_FOURCC2Str( tag, st );
		
		switch( ui32Tag ) {
		case LIST_TAG:
			read( &ui32Tag, sizeof( uint32 ) );
//			AP_FOURCC2Str( tag, st );
			
//			TRACE( "LIST %s   size %u bytes.\n", st, chunksize );
			
			switch( ui32Tag ) {
			case MOVI_TAG:
				if( !bDoneMovi ) {
					bDoneMovi = true;
					ui32MoviPos = ui32FilePos;
				}
				break;

			case HDRL_TAG:
				if( !bDoneHdrl ) {
					bDoneHdrl = true;
					ui32HdrlPos = ui32FilePos;
					handle_hdrl( &m_rMainHdr, &m_rStreamHeader, &m_pStreamFormat,
									ui32ChunkSize, bHasVideo );

					if( m_pStreamFormat ) {
						// Fill in the targe fomat
						m_pTargetFormat = (BITMAPINFOHEADER*) new uint8[m_pStreamFormat->biSize];
						// Copy stuff from the source
						memcpy( m_pTargetFormat, m_pStreamFormat, m_pStreamFormat->biSize );
						// Setup the desired format
						m_pTargetFormat->biBitCount = 24;
						m_pTargetFormat->biCompression = BI_RGB;
						m_pTargetFormat->biSizeImage = m_pTargetFormat->biHeight * m_pTargetFormat->biWidth * 3;
						m_pTargetFormat->biHeight = m_pTargetFormat->biHeight;
					}
				}
			}
			break;

		case IDX1_TAG:

//            TRACE( "IDX1 chunk, %u elements.\n", ui32ChunkSize / 16 );

            if( !bIdxDone ) {

                int32	i32ExtPos = -1;
                uint32	i = 0;
			
                bIdxDone = true;
                ui32IdxPos = ui32FilePos;                   

				if( bHasVideo )
					m_pFrameIndices = new FrameIndexS[m_rMainHdr.dwTotalFrames];

//                if(vf->hasvideo)p->vindex=(vid_idx *)malloc(sizeof(vid_idx)*p->avih->frames);
//                if(vf->hasaudio)p->aindex=(aud_idx *)malloc(sizeof(aud_idx)*p->avih->frames);
				while( i < ui32ChunkSize / 16 ) {

					uint32	ui32ChunkTag;
					read( &ui32ChunkTag, 4 );

					switch( ui32ChunkTag ) {

					case _00DB_TAG:
					case _00IV_TAG:
					case _00DC_TAG:
						if( bHasVideo && (ui32FrameCount < m_rMainHdr.dwTotalFrames) ) {
							read( &m_pFrameIndices[ui32FrameCount], 12 );
							if( i32ExtPos == -1 ) {
								if( m_pFrameIndices[ui32FrameCount].dwPos < ui32MoviPos ) 
									i32ExtPos = ui32MoviPos + 8;
								else
									i32ExtPos = 0;
							}
							m_pFrameIndices[ui32FrameCount].dwPos += i32ExtPos;

//							TRACE( "Frame %03d:  flags: 0x%08x, pos: %d, len: %d\n", ui32FrameCount, m_pFrameIndices[ui32FrameCount].dwFlags,
//								m_pFrameIndices[ui32FrameCount].dwPos, m_pFrameIndices[ui32FrameCount].dwLength );

//							TRACE( "VHDR_KEYFRAME = %d\n", VHDR_KEYFRAME );

							ui32FrameCount++;
						}
						break;

					default:
						seek( 12, DATA_SEEK_CUR );
						break;
                    }
                    i++;
                }
            }

			break;
			
//		default:
//			TRACE( "Unexpected tag %s   size %u bytes.\n", st, chunksize );
		}
		ui32FilePos += 8 + ui32ChunkSize;
		seek( ui32FilePos, DATA_SEEK_SET );
	}
	
	if( !bDoneMovi ) {
//		TRACE( "No movi LIST.\n" );
		return false;
	}

	if( !ui32FrameCount ) {
	
		uint32	ui32MoviSize;
		
		seek( ui32MoviPos + 4, DATA_SEEK_SET );
		read( &ui32MoviSize, 4 );
		seek( ui32MoviPos + 12, DATA_SEEK_SET );
		ui32FilePos = ui32MoviPos + 12;
		

		if( bHasVideo )
			m_pFrameIndices = new FrameIndexS[m_rMainHdr.dwTotalFrames];

		while( (ui32FilePos < ui32MoviPos + ui32MoviSize) && (ui32FilePos < ui32DataSize) ) {
			uint32	ui32MoviTag;
			uint32	ui32Length;

			read( &ui32MoviTag, 4 );
			read( &ui32Length, 4 );

			ui32Length = ((ui32Length - 1) | 1) + 1;

			if( ui32FilePos + ui32Length + 8 < ui32DataSize ) {
				switch( ui32MoviTag ) {
				case _00DB_TAG:
				case _00IV_TAG:
				case _00DC_TAG:
					if( bHasVideo && (ui32FrameCount < m_rMainHdr.dwTotalFrames) ) {
						m_pFrameIndices[ui32FrameCount].dwFlags = 0;
						m_pFrameIndices[ui32FrameCount].dwPos = ui32FilePos;
						m_pFrameIndices[ui32FrameCount].dwLength = ui32Length;
						ui32FrameCount++;
					}
					break;
				case LIST_TAG:
					ui32Length = 4;
					break;
				}
			}
			ui32FilePos += 8 + ui32Length;
			seek( ui32Length, DATA_SEEK_CUR );
		}
	}

	if( bHasVideo && ui32FrameCount ) {

		if( m_pStreamFormat ) {
			m_i32Width = m_pStreamFormat->biWidth;
			m_i32Height = m_pStreamFormat->biHeight;
			m_i32BPP = m_pStreamFormat->biBitCount;

			// Open decompressor
			m_hIC = ICDecompressOpen( ICTYPE_VIDEO, m_rStreamHeader.fccHandler, m_pStreamFormat, m_pTargetFormat );

			if( m_hIC ) {
				ICDecompressBegin( m_hIC, m_pStreamFormat, m_pTargetFormat );
			}
		}

	}

	return 0;
}

uint8*
AVIPlayerC::decompress_frame( PajaTypes::int32 i32Frame )
{
	if( !m_hIC )
		return 0;

	// Clamp frame
	while( i32Frame < 0 )
		i32Frame += get_frames();
	while( i32Frame >= get_frames() )
		i32Frame -= get_frames();

	if( m_i32CurFrame == i32Frame )
		return m_pFrameBuffer;

	// In data
	uint8*	pData = 0;

	// Allocate frame buffer.
	if( !m_pFrameBuffer )
		m_pFrameBuffer = new uint8[m_pTargetFormat->biSizeImage];

	if( m_pFrameIndices[i32Frame].dwFlags & INDEX_KEYFRAME ) {
		// Simple case, we just landed at keyframe.
		pData = &m_pData[m_pFrameIndices[i32Frame].dwPos + 8];
		ICDecompress( m_hIC, ICDECOMPRESS_UPDATE , m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
	}
	else if( i32Frame < m_i32CurFrame ) {
		// We are reversing -> render from nearest keyframe
		int32	i = i32Frame;
		int32	i32KeyFrame = -1;

		while( i >= 0 && i < get_frames() ) {
			if( m_pFrameIndices[i].dwFlags & INDEX_KEYFRAME ) {
				i32KeyFrame = i;
				break;
			}
			i--;
		}
		
		// No keyframe found, just render the frame. possibly errors.
		if( i32KeyFrame == -1 ) {
			pData = &m_pData[m_pFrameIndices[i32Frame].dwPos + 8];

			if( m_pFrameIndices[i32Frame].dwFlags & INDEX_KEYFRAME )
				ICDecompress( m_hIC, ICDECOMPRESS_UPDATE, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
			else
				ICDecompress( m_hIC, ICDECOMPRESS_UPDATE | ICDECOMPRESS_NOTKEYFRAME, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
		}
		else {
			// Preroll
			for( i = i32KeyFrame; i < i32Frame; i++ ) {
				pData = &m_pData[m_pFrameIndices[i].dwPos + 8];
				if( m_pFrameIndices[i].dwFlags & INDEX_KEYFRAME )
					ICDecompress( m_hIC, ICDECOMPRESS_PREROLL | ICDECOMPRESS_HURRYUP, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
				else
					ICDecompress( m_hIC, ICDECOMPRESS_NOTKEYFRAME | ICDECOMPRESS_PREROLL | ICDECOMPRESS_HURRYUP, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
			}
			// Draw the actual frame
			pData = &m_pData[m_pFrameIndices[i32Frame].dwPos + 8];
			if( m_pFrameIndices[i32Frame].dwFlags & INDEX_KEYFRAME )
				ICDecompress( m_hIC, ICDECOMPRESS_UPDATE, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
			else
				ICDecompress( m_hIC, ICDECOMPRESS_UPDATE | ICDECOMPRESS_NOTKEYFRAME, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
		}
	}
	else { //if( i32Frame > m_i32CurFrame ) {
		// We are fast forwarding -> render the frames between
		int32	i;

		// Preroll
		for( i = m_i32CurFrame + 1; i < i32Frame; i++ ) {
			pData = &m_pData[m_pFrameIndices[i].dwPos + 8];
			if( m_pFrameIndices[i].dwFlags & INDEX_KEYFRAME )
				ICDecompress( m_hIC, ICDECOMPRESS_PREROLL | ICDECOMPRESS_HURRYUP, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
			else
				ICDecompress( m_hIC, ICDECOMPRESS_NOTKEYFRAME | ICDECOMPRESS_PREROLL | ICDECOMPRESS_HURRYUP, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
		}
		// Draw the actual frame
		pData = &m_pData[m_pFrameIndices[i32Frame].dwPos + 8];
		if( m_pFrameIndices[i32Frame].dwFlags & INDEX_KEYFRAME )
			ICDecompress( m_hIC, ICDECOMPRESS_UPDATE, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
		else
			ICDecompress( m_hIC, ICDECOMPRESS_UPDATE | ICDECOMPRESS_NOTKEYFRAME, m_pStreamFormat, pData, m_pTargetFormat, m_pFrameBuffer );
	}

	m_i32CurFrame = i32Frame;

	return m_pFrameBuffer;
}

int32
AVIPlayerC::get_width() const
{
	return m_i32Width;
}

int32
AVIPlayerC::get_height() const
{
	return m_i32Height;
}

int32
AVIPlayerC::get_bpp() const
{
	return m_i32BPP;
}
	
int32
AVIPlayerC::get_frames() const
{
	return m_rMainHdr.dwTotalFrames;
}
	
float32
AVIPlayerC::get_fps() const
{
	return (float32)m_rStreamHeader.dwRate / (float32)m_rStreamHeader.dwScale;
}

uint8*
AVIPlayerC::get_data()
{
	return m_pFrameBuffer;
}
	
