// Insert your headers here
#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <string>
#include "jpegimp.h"

#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "EditableI.h"
#include "UndoC.h"
#include "ImportableImageI.h"
#include "DeviceContextC.h"
#include "DeviceInterfaceI.h"
#include "OpenGLViewportC.h"
#include "OpenGLDeviceC.h"
#include "ImageResampleC.h"

#include "ijl.h"


using namespace PajaTypes;
using namespace Edit;
using namespace FileIO;
using namespace Import;
using namespace PluginClass;
using namespace PajaSystem;
using namespace JpegImport;


JPEGImportDescC	g_rJPEGImportDesc;


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 );
}


#ifndef PAJAPLAYER

//
// The DLL
//

BOOL APIENTRY
DllMain( HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved )
{
    switch( ulReasonForCall )
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    return TRUE;
}



__declspec( dllexport )
int32
get_classdesc_count()
{
	return 1;
}

__declspec( dllexport )
ClassDescC*
get_classdesc( int32 i )
{
	if( i == 0 )
		return &g_rJPEGImportDesc;
	return 0;
}

__declspec( dllexport )
int32
get_api_version()
{
	return DEMOPAJA_VERSION;
}

__declspec( dllexport )
char*
get_dll_name()
{
	return "jpegimp.dll - JPEG Importer (c)2000 memon/moppi productions";
}

#endif

//
// TARGA import
//


JPEGImportC::JPEGImportC() :
	m_pCompressedData( 0 ),
	m_i32CompressedSize( 0 ),
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_ui32TextureId( 0 )
{
	m_rTexBounds[0] = Vector2C( 0, 0 );
	m_rTexBounds[1] = Vector2C( 1, 1 );
}

JPEGImportC::JPEGImportC( EditableI* pOriginal ) :
	ImportableImageI( pOriginal ),
	m_pCompressedData( 0 ),
	m_i32CompressedSize( 0 ),
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_ui32TextureId( 0 )
{
	m_rTexBounds[0] = Vector2C( 0, 0 );
	m_rTexBounds[1] = Vector2C( 1, 1 );
}

JPEGImportC::~JPEGImportC()
{
	if( get_original() )
		return;

	// delete creted textures
	glDeleteTextures( 1, &m_ui32TextureId );

	delete [] m_pCompressedData;
	delete [] m_pData;
}

JPEGImportC*
JPEGImportC::create_new()
{
	return new JPEGImportC;
}

DataBlockI*
JPEGImportC::create()
{
	return new JPEGImportC;
}

DataBlockI*
JPEGImportC::create( EditableI* pOriginal )
{
	return new JPEGImportC( pOriginal );
}

void
JPEGImportC::copy( EditableI* pEditable )
{
	// empty
}

void
JPEGImportC::restore( EditableI* pEditable )
{
	JPEGImportC*	pFile = (JPEGImportC*)pEditable;

	m_ui32TextureId = pFile->m_ui32TextureId;
	m_pData = pFile->m_pData;
	m_i32Width = pFile->m_i32Width;
	m_i32Height = pFile->m_i32Height;
	m_i32Bpp = pFile->m_i32Bpp;
	m_sFileName = pFile->m_sFileName;
	m_i32CompressedSize = pFile->m_i32CompressedSize;
	m_pCompressedData = pFile->m_pCompressedData;
}


// the interface

// returns the name of the file this importable refers to
const char*
JPEGImportC::get_filename()
{
	return m_sFileName.c_str();
}

void
JPEGImportC::set_filename( const char* szName )
{
	m_sFileName = szName;
}


bool
JPEGImportC::uncompress()
{
	delete [] m_pData;
	m_pData = 0;

    JPEG_CORE_PROPERTIES	image;
    memset( &image, 0, sizeof( JPEG_CORE_PROPERTIES ) );

    if( ijlInit( &image ) != IJL_OK ) {
        OutputDebugString( "Cannot initialize Intel JPEG library\n" );
        return false;
    }

	image.JPGBytes = m_pCompressedData;
	image.JPGSizeBytes = m_i32CompressedSize;
    
    if( ijlRead( &image, IJL_JBUFF_READPARAMS ) != IJL_OK )
    {
        OutputDebugString( "Cannot read JPEG file header file\n" );
		return false;
    }

    // !dudnik to fix bug [287A305B]
    // Set the JPG color space ... this will always be
    // somewhat of an educated guess at best because JPEG
    // is "color blind" (i.e., nothing in the bit stream
    // tells you what color space the data was encoded from).
    // However, in this example we assume that we are
    // reading JFIF files which means that 3 channel images
    // are in the YCbCr color space and 1 channel images are
    // in the Y color space.
    switch( image.JPGChannels )
    {
    case 1:
      image.JPGColor = IJL_G;
	  image.DIBColor = IJL_G;
	  image.DIBChannels = 1;
      break;

    case 3:
      image.JPGColor = IJL_YCBCR;
	  image.DIBColor = IJL_RGB;
	  image.DIBChannels = 3;
      break;

    default:
      // This catches everything else, but no
      // color twist will be performed by the IJL.
      image.DIBColor = (IJL_COLOR)IJL_OTHER;
      image.JPGColor = (IJL_COLOR)IJL_OTHER;
      break;
    }

    // !dudnik: It needs to be considered later...
    image.DIBPadBytes = 0;


	m_i32Width = image.JPGWidth;
	m_i32Height = image.JPGHeight;
	m_i32Bpp = image.DIBChannels * 8;

    int32	i32ImageSize = image.JPGWidth * image.DIBChannels * image.JPGHeight;

    m_pData = new uint8[i32ImageSize];
    if( !m_pData ) {
        OutputDebugString( "Cannot allocate memory for image\n" );
        return false;
    }

    image.DIBBytes = m_pData;
    image.DIBWidth = image.JPGWidth;
    image.DIBHeight = -(int)image.JPGHeight;	// flip the image

    if( ijlRead( &image, IJL_JBUFF_READWHOLEIMAGE ) != IJL_OK )
    {
        OutputDebugString( "Cannot read image data from file\n" );
        delete[] m_pData;
		m_pData = 0;
        return false;
    }

    if( ijlFree( &image ) != IJL_OK ) {
        OutputDebugString( "Cannot free Intel(R) JPEG library" );
    }

	return true;
}


// loads the file
bool
JPEGImportC::load_file( const char* szName, DemoInterfaceC* pInterface )
{
	// Store interace pointer
	m_pDemoInterface = pInterface;

	FILE*	pStream;

	pStream = fopen( pInterface->get_absolute_path( szName ), "rb" );
	if( !pStream )
		return false;

	// Store file name
	m_sFileName = szName;

	fseek( pStream, 0, SEEK_END );

	m_i32CompressedSize = ftell( pStream );

	delete [] m_pCompressedData;
	m_pCompressedData = 0;
	m_pCompressedData = new uint8[m_i32CompressedSize];

	fseek( pStream, 0, SEEK_SET );

	if( fread( m_pCompressedData, m_i32CompressedSize, 1, pStream ) != 1 ) {
		fclose( pStream );
		return false;
	}

	fclose( pStream );

	if( !uncompress() )
		return false;

	return true;
}

void
JPEGImportC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	ImportableI::initialize( ui32Reason, pInterface );

	DeviceContextC* pContext = m_pDemoInterface->get_device_context();
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();

	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	if( ui32Reason == INIT_INITIAL_UPDATE ) {
		if( !m_ui32TextureId )
			upload_texture();
	}
}

ClassIdC
JPEGImportC::get_class_id()
{
	return CLASS_JPEG_IMPORT;
}

const char*
JPEGImportC::get_class_name()
{
	return "JPEG Image";
}


// interface for this class

float32
JPEGImportC::get_width()
{
	return (float32)m_i32Width;
}

float32
JPEGImportC::get_height()
{
	return (float32)m_i32Height;
}

int32
JPEGImportC::get_data_width()
{
	return m_i32Width;
}

int32
JPEGImportC::get_data_height()
{
	return m_i32Height;
}

int32
JPEGImportC::get_data_pitch()
{
	return m_i32Width;
}

BBox2C&
JPEGImportC::get_tex_coord_bounds()
{
	return m_rTexBounds;
}

int32
JPEGImportC::get_data_bpp()
{
	return m_i32Bpp;
}

uint8*
JPEGImportC::get_data()
{
	return m_pData;
}


inline
uint32
lowest_bit_mask( uint32 v )
{
	return (v & -v);
}

static
uint32
ceil_power2( uint32 ui32Num )
{
	uint32	i = lowest_bit_mask( ui32Num );
	while( i < ui32Num )
		i <<= 1;
	return i;
}

static
int32
nearest_power2( int32 i32Num )
{
	int32	i = ceil_power2( i32Num );	// bigger
	int32	j = i / 2;					// smaller

	int32	i32DiffI = i - i32Num;
	int32	i32DiffJ = i32Num - j;

	// diff J has to be twice as little as diffI to be chosen.
	i32DiffI /= 2;

	if( i32DiffJ < i32DiffI )
		return j;

	return i;
}


void
JPEGImportC::upload_texture()
{
	glGenTextures( 1, &m_ui32TextureId );
	glBindTexture( GL_TEXTURE_2D, m_ui32TextureId );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );


	uint8*	pResampleData = 0;

	int32	i32Width = m_i32Width;
	int32	i32Height = m_i32Height;
	uint8*	pData = m_pData;

	int32	i32WidthPow2 = nearest_power2( m_i32Width );
	int32	i32HeightPow2 = nearest_power2( m_i32Height );

	if( i32WidthPow2 != m_i32Width || i32HeightPow2 != m_i32Height ) {
		TRACE( "JPEG imp: rescaling %d x %d -> %d x %d\n", m_i32Width, m_i32Height, i32WidthPow2, i32HeightPow2 );
		pResampleData = new uint8[i32WidthPow2 * i32HeightPow2 * (m_i32Bpp / 8)];
		ImageResampleC::resample_bilinear( m_i32Bpp, m_pData, m_i32Width, m_i32Height, pResampleData, i32WidthPow2, i32HeightPow2 );
		pData = pResampleData;
		i32Width = i32WidthPow2;
		i32Height = i32HeightPow2;
	}

	if( m_i32Bpp == 8 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA8, i32Width, i32Height, 0, GL_ALPHA , GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp == 24 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, i32Width, i32Height, 0, GL_RGB, GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp == 32 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, i32Width, i32Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData );
	}

	GLenum	eError = glGetError();
	if( eError ) {
		TRACE( "JPEGImportC::upload_texture: %s\n", (const char*)gluErrorString( eError ) );
	}

	if( pResampleData )
		delete [] pResampleData;
}


void
JPEGImportC::bind_texture( DeviceInterfaceI* pInterface, PajaTypes::uint32 ui32Stage, uint32 ui32Properties )
{
	if( pInterface->get_class_id() != CLASS_OPENGL_DEVICEDRIVER )
		return;

	if( !m_ui32TextureId ) {
		upload_texture();
	}
	else
		glBindTexture( GL_TEXTURE_2D, m_ui32TextureId );

	if( ui32Properties & IMAGE_LINEAR ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	}

	if( ui32Properties & IMAGE_CLAMP ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	}

}

const char*
JPEGImportC::get_info()
{
	static char	szInfo[256];
	_snprintf( szInfo, 255, "%d x %d x %dbit (%dkB)", m_i32Width, m_i32Height, m_i32Bpp, m_i32CompressedSize / 1024 );
	return szInfo;
}

ClassIdC
JPEGImportC::get_default_effect()
{
	return CLASS_IMAGE_EFFECT;
}

int32
JPEGImportC::get_duration()
{
	return -1;
}

float32
JPEGImportC::get_start_label()
{
	return 0;
}

float32
JPEGImportC::get_end_label()
{
	return 0;
}

void
JPEGImportC::eval_state( int32 i32Time )
{
	// empty
}



enum JPEGImportChunksE {
	CHUNK_JPEGIMPORT_BASE =	0x1000,
	CHUNK_JPEGIMPORT_DATA =	0x2000,
};

const uint32	JPEGIMPORT_VERSION = 1;


uint32
JPEGImportC::save( SaveC* pSave )
{
	uint32		ui32Error = IO_OK;
	uint8		ui8Tmp;
	std::string	sStr;

	// file base
	pSave->begin_chunk( CHUNK_JPEGIMPORT_BASE, JPEGIMPORT_VERSION );
		sStr = m_sFileName;
		if( sStr.size() > 255 )
			sStr.resize( 255 );
		ui32Error = pSave->write_str( sStr.c_str() );
	pSave->end_chunk();

	// file data
	pSave->begin_chunk( CHUNK_JPEGIMPORT_DATA, JPEGIMPORT_VERSION );
		ui32Error = pSave->write( &m_i32Width, sizeof( m_i32Width ) );
		ui32Error = pSave->write( &m_i32Height, sizeof( m_i32Height ) );
		ui8Tmp = (uint8)m_i32Bpp;
		ui32Error = pSave->write( &ui8Tmp, sizeof( ui8Tmp ) );
		ui32Error = pSave->write( &m_i32CompressedSize, sizeof( m_i32CompressedSize ) );
		ui32Error = pSave->write( m_pCompressedData, m_i32CompressedSize );
	pSave->end_chunk();

	return ui32Error;
}

uint32
JPEGImportC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];
	uint8	ui8Tmp;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_JPEGIMPORT_BASE:
			{
				if( pLoad->get_chunk_version() == JPEGIMPORT_VERSION ) {
					ui32Error = pLoad->read_str( szStr );
					m_sFileName = szStr;
				}
			}
			break;

		case CHUNK_JPEGIMPORT_DATA:
			{
				if( pLoad->get_chunk_version() == JPEGIMPORT_VERSION ) {
					ui32Error = pLoad->read( &m_i32Width, sizeof( m_i32Width ) );
					ui32Error = pLoad->read( &m_i32Height, sizeof( m_i32Height ) );
					ui32Error = pLoad->read( &ui8Tmp, sizeof( ui8Tmp ) );
					m_i32Bpp = ui8Tmp;
					// delete old data if any
					delete [] m_pData;
					delete [] m_pCompressedData;
					ui32Error = pLoad->read( &m_i32CompressedSize, sizeof( m_i32CompressedSize ) );
					m_pCompressedData = new uint8[m_i32CompressedSize];
					ui32Error = pLoad->read( m_pCompressedData, m_i32CompressedSize );

					if( !uncompress() )
						ui32Error = IO_ERROR_READ;
				}
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END ) {
			return ui32Error;
		}
	}

	if( ui32Error != IO_OK && ui32Error != IO_END ) {
		return ui32Error;
	}

	return IO_OK;
}
