//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose: 
//
//===========================================================================//

#include <stdlib.h>
#include <stdio.h>
#include "tier0/dbg.h"
#include <malloc.h>
#include "filesystem.h"
#include "bitmap/tgawriter.h"
#include "tier1/utlbuffer.h"
#include "bitmap/imageformat.h"
#include "tier2/tier2.h"
#include "tier2/fileutils.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

namespace TGAWriter
{

#pragma pack(1)
struct TGAHeader_t
{
	unsigned char 	id_length;
	unsigned char   colormap_type;
	unsigned char   image_type;
	unsigned short	colormap_index;
	unsigned short  colormap_length;
	unsigned char	colormap_size;
	unsigned short	x_origin;
	unsigned short	y_origin;
	unsigned short	width;
	unsigned short	height;
	unsigned char	pixel_size;
	unsigned char	attributes;
};
#pragma pack()

#define fputc myfputc
#define fwrite myfwrite

static void fputLittleShort( unsigned short s, CUtlBuffer &buffer )
{
	buffer.PutChar( s & 0xff );
	buffer.PutChar( s >> 8 );
}

static inline void myfputc( unsigned char c, FileHandle_t fileHandle )
{
	g_pFullFileSystem->Write( &c, 1, fileHandle );
}

static inline void myfwrite( void const *data, int size1, int size2, FileHandle_t fileHandle )
{
	g_pFullFileSystem->Write( data, size1 * size2, fileHandle );
}


//-----------------------------------------------------------------------------
// FIXME: assumes that we don't need to do gamma correction.
//-----------------------------------------------------------------------------
bool WriteToBuffer( unsigned char *pImageData, CUtlBuffer &buffer, int width, int height, 
					ImageFormat srcFormat, ImageFormat dstFormat )
{
	TGAHeader_t header;

	// Fix the dstFormat to match what actually is going to go into the file
	switch( dstFormat )
	{
	case IMAGE_FORMAT_RGB888:
		dstFormat = IMAGE_FORMAT_BGR888;
		break;
#if defined( _X360 )
	case IMAGE_FORMAT_LINEAR_RGB888:
		dstFormat = IMAGE_FORMAT_LINEAR_BGR888;
		break;
#endif
	case IMAGE_FORMAT_RGBA8888:
		dstFormat = IMAGE_FORMAT_BGRA8888;
		break;
	}

	header.id_length = 0; // comment length
	header.colormap_type = 0; // ???

	switch( dstFormat )
	{
	case IMAGE_FORMAT_BGR888:
#if defined( _X360 )
	case IMAGE_FORMAT_LINEAR_BGR888:
#endif
		header.image_type = 2; // 24/32 bit uncompressed TGA
		header.pixel_size = 24;
		break;
	case IMAGE_FORMAT_BGRA8888:
		header.image_type = 2; // 24/32 bit uncompressed TGA
		header.pixel_size = 32;
		break;
	case IMAGE_FORMAT_I8:
		header.image_type = 1; // 8 bit uncompressed TGA
		header.pixel_size = 8;
		break;
	default:
		return false;
		break;
	}

	header.colormap_index = 0;
	header.colormap_length = 0;
	header.colormap_size = 0;
	header.x_origin = 0;
	header.y_origin = 0;
	header.width = ( unsigned short )width;
	header.height = ( unsigned short )height;
	header.attributes = 0x20;	// Makes it so we don't have to vertically flip the image

	buffer.PutChar( header.id_length );
	buffer.PutChar( header.colormap_type );
	buffer.PutChar( header.image_type );
	fputLittleShort( header.colormap_index, buffer );
	fputLittleShort( header.colormap_length, buffer );
	buffer.PutChar( header.colormap_size );
	fputLittleShort( header.x_origin, buffer );
	fputLittleShort( header.y_origin, buffer );
	fputLittleShort( header.width, buffer );
	fputLittleShort( header.height, buffer );
	buffer.PutChar( header.pixel_size );
	buffer.PutChar( header.attributes );

	int nSizeInBytes = width * height * ImageLoader::SizeInBytes( dstFormat );
	buffer.EnsureCapacity( buffer.TellPut() + nSizeInBytes );
	unsigned char *pDst = (unsigned char*)buffer.PeekPut();

	if ( !ImageLoader::ConvertImageFormat( pImageData, srcFormat, pDst, dstFormat, width, height ) )
		return false;

	buffer.SeekPut( CUtlBuffer::SEEK_CURRENT, nSizeInBytes );
	return true;
}

bool WriteDummyFileNoAlloc( const char *fileName, int width, int height, enum ImageFormat dstFormat )
{
	TGAHeader_t tgaHeader;

	Assert( g_pFullFileSystem );
	if( !g_pFullFileSystem )
	{
		return false;
	}
	COutputFile fp( fileName );

	int nBytesPerPixel, nImageType, nPixelSize;

	switch( dstFormat )
	{
	case IMAGE_FORMAT_BGR888:
#if defined( _X360 )
	case IMAGE_FORMAT_LINEAR_BGR888:
#endif
		nBytesPerPixel = 3; // 24/32 bit uncompressed TGA
		nPixelSize = 24;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_BGRA8888:
		nBytesPerPixel = 4; // 24/32 bit uncompressed TGA
		nPixelSize = 32;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_I8:
		nBytesPerPixel = 1; // 8 bit uncompressed TGA
		nPixelSize = 8;
		nImageType = 1;
		break;
	default:
		return false;
		break;
	}

    memset( &tgaHeader, 0, sizeof(tgaHeader) );
    tgaHeader.id_length  = 0;
    tgaHeader.image_type = (unsigned char) nImageType;
    tgaHeader.width      = (unsigned short) width;
    tgaHeader.height     = (unsigned short) height;
    tgaHeader.pixel_size = (unsigned char) nPixelSize;
    tgaHeader.attributes = 0x20;

    // Write the Targa header
	fp.Write( &tgaHeader, sizeof(TGAHeader_t) );

	// Write out width * height black pixels
	unsigned char black[4] = { 0x1E, 0x9A, 0xFF, 0x00 };
	for (int i = 0; i < width * height; i++)
	{
		fp.Write( black, nBytesPerPixel );
	}

	return true;
}

bool WriteTGAFile( const char *fileName, int width, int height, enum ImageFormat srcFormat, uint8 const *srcData, int nStride )
{
	TGAHeader_t tgaHeader;

	COutputFile fp( fileName );

	int nBytesPerPixel, nImageType, nPixelSize;

	bool bMustConvert = false;
	ImageFormat dstFormat = srcFormat;
 
	switch( srcFormat )
	{
	case IMAGE_FORMAT_BGR888:
#if defined( _X360 )
	case IMAGE_FORMAT_LINEAR_BGR888:
#endif
		nBytesPerPixel = 3; // 24/32 bit uncompressed TGA
		nPixelSize = 24;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_BGRA8888:
		nBytesPerPixel = 4; // 24/32 bit uncompressed TGA
		nPixelSize = 32;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_RGBA8888:
		bMustConvert = true;
		dstFormat = IMAGE_FORMAT_BGRA8888;
		nBytesPerPixel = 4; // 24/32 bit uncompressed TGA
		nPixelSize = 32;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_I8:
		nBytesPerPixel = 1; // 8 bit uncompressed TGA
		nPixelSize = 8;
		nImageType = 1;
		break;
	default:
		return false;
		break;
	}

    memset( &tgaHeader, 0, sizeof(tgaHeader) );
    tgaHeader.id_length  = 0;
    tgaHeader.image_type = (unsigned char) nImageType;
    tgaHeader.width      = (unsigned short) width;
    tgaHeader.height     = (unsigned short) height;
    tgaHeader.pixel_size = (unsigned char) nPixelSize;
    tgaHeader.attributes = 0x20;

    // Write the Targa header
	fp.Write( &tgaHeader, sizeof(TGAHeader_t) );

	// Write out image data
	if ( bMustConvert )
	{
		uint8 *pLineBuf = new uint8[ nBytesPerPixel * width ];
		while( height-- )
		{
			ImageLoader::ConvertImageFormat( srcData, srcFormat, pLineBuf, dstFormat, width, 1 );
			fp.Write( pLineBuf, nBytesPerPixel * width );
			srcData += nStride;
		}
		delete[] pLineBuf;
	}
	else
	{
		while( height-- )
		{
			fp.Write( srcData, nBytesPerPixel * width );
			srcData += nStride;
		}
		
	}
	return true;
}


bool WriteRectNoAlloc( unsigned char *pImageData, const char *fileName, int nXOrigin, int nYOrigin, int width, int height, int nStride, enum ImageFormat srcFormat )
{
	Assert( g_pFullFileSystem );
	if( !g_pFullFileSystem )
	{
		return false;
	}
	FileHandle_t fp;
	fp = g_pFullFileSystem->Open( fileName, "r+b" );

	//
	// Read in the targa header
	//
	TGAHeader_t tgaHeader;
	g_pFullFileSystem->Read( &tgaHeader, sizeof(tgaHeader), fp );


	int nBytesPerPixel, nImageType, nPixelSize;

	switch( srcFormat )
	{
	case IMAGE_FORMAT_BGR888:
#if defined( _X360 )
	case IMAGE_FORMAT_LINEAR_BGR888:
#endif
		nBytesPerPixel = 3; // 24/32 bit uncompressed TGA
		nPixelSize = 24;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_BGRA8888:
		nBytesPerPixel = 4; // 24/32 bit uncompressed TGA
		nPixelSize = 32;
		nImageType = 2;
		break;
	case IMAGE_FORMAT_I8:
		nBytesPerPixel = 1; // 8 bit uncompressed TGA
		nPixelSize = 8;
		nImageType = 1;
		break;
	default:
		return false;
		break;
	}

	// Verify src data matches the targa we're going to write into
	if ( nPixelSize != tgaHeader.pixel_size )
	{
		Warning( "TGA doesn't match source data.\n" );
		return false;
	}


	// Seek to the origin of the target subrect from the beginning of the file
	g_pFullFileSystem->Seek( fp, nBytesPerPixel * (tgaHeader.width * nYOrigin + nXOrigin), FILESYSTEM_SEEK_CURRENT );

	unsigned char *pSrc = pImageData;

	// Run through each scanline of the incoming rect
	for (int row=0; row < height; row++ )
	{
		g_pFullFileSystem->Write( pSrc, nBytesPerPixel * width, fp );

		// Advance src pointer to next scanline
		pSrc += nBytesPerPixel * nStride;

		// Seek ahead in the file
		g_pFullFileSystem->Seek( fp, nBytesPerPixel * ( tgaHeader.width - width ), FILESYSTEM_SEEK_CURRENT );
	}

	g_pFullFileSystem->Close( fp );
	return true;
}

} // end namespace TGAWriter

