//
// BrightnessContrastPlugin.cpp
//
// BrightnessContrast Plugin
//
// Copyright (c) 2000 memon/moppi productions
//

//#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 "glext.h"
#include "extgl.h"

#include "ColorMatrix.h"

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableI.h"
#include "OpenGLDeviceC.h"
#include "OpenGLViewportC.h"
#include "ColorPlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImportableImageI.h"

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

using namespace LevelsPlugin;


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







//////////////////////////////////////////////////////////////////////////
//
//  Levels effect class descriptor.
//

LevelsDescC::LevelsDescC()
{
	// empty
}

LevelsDescC::~LevelsDescC()
{
	// empty
}

void*
LevelsDescC::create()
{
	return (void*)LevelsEffectC::create_new();
}

int32
LevelsDescC::get_classtype() const
{
	return CLASS_TYPE_EFFECT;
}

SuperClassIdC
LevelsDescC::get_super_class_id() const
{
	return SUPERCLASS_EFFECT;
}

ClassIdC
LevelsDescC::get_class_id() const
{
	return CLASS_LEVELS_EFFECT;
};

const char*
LevelsDescC::get_name() const
{
	return "Levels";
}

const char*
LevelsDescC::get_desc() const
{
	return "Levels Adjustment Effect";
}

const char*
LevelsDescC::get_author_name() const
{
	return "Mikko \"memon\" Mononen";
}

const char*
LevelsDescC::get_copyright_message() const
{
	return "Copyright (c) 2003 Moppi Productions";
}

const char*
LevelsDescC::get_url() const
{
	return "http://moppi.inside.org/demopaja/";
}

const char*
LevelsDescC::get_help_filename() const
{
	return "res://LevelsHelp.html";
}

uint32
LevelsDescC::get_required_device_driver_count() const
{
	return 1;
}

const ClassIdC&
LevelsDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_OPENGL_DEVICEDRIVER;
}


uint32
LevelsDescC::get_ext_count() const
{
	return 0;
}

const char*
LevelsDescC::get_ext( uint32 ui32Index ) const
{
	return 0;
}


//////////////////////////////////////////////////////////////////////////
//
// Global descriptors, which will be returned to the Demopaja.
//

LevelsDescC				g_rLevelsDesc;


//////////////////////////////////////////////////////////////////////////
//
// The effect
//

LevelsEffectC::LevelsEffectC()
{
	//
	// Create Transform gizmo.
	//
	m_pTraGizmo = AutoGizmoC::create_new( this, "Transform", ID_GIZMO_TRANS );

	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Position", Vector2C(), ID_TRANSFORM_POS,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE ) );
	
	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Pivot", Vector2C(), ID_TRANSFORM_PIVOT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_WORLD_SPACE, PARAM_ANIMATABLE ) );
	
	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Scale", Vector2C( 1, 1 ), ID_TRANSFORM_SCALE,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f ) );


	//
	// Create Input gizmo.
	//
	m_pInGizmo = AutoGizmoC::create_new( this, "Input", ID_GIZMO_INPUT );

	// Input Low
	m_pInGizmo->add_parameter(	ParamFloatC::create_new( m_pInGizmo, "Low", 0.0f, ID_INPUT_LOW,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0.0f, 1.0f, 0.01f ) );

	// Input High
	m_pInGizmo->add_parameter(	ParamFloatC::create_new( m_pInGizmo, "High", 1.0f, ID_INPUT_HIGH,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0.0f, 1.0f, 0.01f ) );

	// Input Gamma
	m_pInGizmo->add_parameter(	ParamFloatC::create_new( m_pInGizmo, "Gamma", 0.0f, ID_INPUT_GAMMA,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, -1.0f, 1.0f, 0.1f ) );

	//
	// Create Output gizmo.
	//
	m_pOutGizmo = AutoGizmoC::create_new( this, "Output", ID_GIZMO_OUTPUT );

	// Output Low
	m_pOutGizmo->add_parameter(	ParamFloatC::create_new( m_pOutGizmo, "Low", 0.0f, ID_OUTPUT_LOW,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0.0f, 1.0f, 0.01f ) );

	// Output High
	m_pOutGizmo->add_parameter(	ParamFloatC::create_new( m_pOutGizmo, "High", 1.0f, ID_OUTPUT_HIGH,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0.0f, 1.0f, 0.01f ) );
}

LevelsEffectC::LevelsEffectC( EditableI* pOriginal ) :
	EffectI( pOriginal ),
	m_pTraGizmo( 0 ),
	m_pInGizmo( 0 ),
	m_pOutGizmo( 0 )
{
	// Empty. The parameters are not created in the clone constructor.
}

LevelsEffectC::~LevelsEffectC()
{
	// Return if this is a clone.
	if( get_original() )
		return;

	// Release gizmos.
	m_pTraGizmo->release();
	m_pInGizmo->release();
	m_pOutGizmo->release();
}

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

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

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

void
LevelsEffectC::copy( EditableI* pEditable )
{
	EffectI::copy( pEditable );

	// Make deep copy.
	LevelsEffectC*	pEffect = (LevelsEffectC*)pEditable;
	m_pTraGizmo->copy( pEffect->m_pTraGizmo );
	m_pInGizmo->copy( pEffect->m_pInGizmo );
	m_pOutGizmo->copy( pEffect->m_pOutGizmo );
}

void
LevelsEffectC::restore( EditableI* pEditable )
{
	EffectI::restore( pEditable );

	// Make shallow copy.
	LevelsEffectC*	pEffect = (LevelsEffectC*)pEditable;
	m_pTraGizmo = pEffect->m_pTraGizmo;
	m_pInGizmo = pEffect->m_pInGizmo;
	m_pOutGizmo = pEffect->m_pOutGizmo;
}

int32
LevelsEffectC::get_gizmo_count()
{
	// Return number of gizmos inside this effect.
	return GIZMO_COUNT;
}

GizmoI*
LevelsEffectC::get_gizmo( PajaTypes::int32 i32Index )
{
	// Returns specified gizmo.
	// Since the ID's are zero based, we can use them as indices.
	switch( i32Index ) {
	case ID_GIZMO_TRANS:
		return m_pTraGizmo;
	case ID_GIZMO_INPUT:
		return m_pInGizmo;
	case ID_GIZMO_OUTPUT:
		return m_pOutGizmo;
	}

	return 0;
}

ClassIdC
LevelsEffectC::get_class_id()
{
	// Return the class ID. Should be same as in the class descriptor.
	return CLASS_LEVELS_EFFECT;
}

const char*
LevelsEffectC::get_class_name()
{
	// Return the class name. Should be same as in the class descriptor.
	return "Levels";
}

void
LevelsEffectC::set_default_file( int32 i32Time, FileHandleC* pHandle )
{
	// empty
}

ParamI*
LevelsEffectC::get_default_param( int32 i32Param )
{
	// Return specified default parameter.
	if( i32Param == DEFAULT_PARAM_POSITION )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_POS );
	else if( i32Param == DEFAULT_PARAM_SCALE )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE );
	else if( i32Param == DEFAULT_PARAM_PIVOT )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT );
	return 0;
}

void
LevelsEffectC::initialize( uint32 ui32Reason, DeviceContextC* pContext, TimeContextC* pTimeContext )
{
	if( ui32Reason == INIT_INITIAL_UPDATE ) {
		init_gl_extension();
	}
}


void
LevelsEffectC::eval_state( int32 i32Time, DeviceContextC* pContext, TimeContextC* pTimeContext )
{
	uint32		i;
	Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;

	Vector2C	rScale;
	Vector2C	rPos;
	Vector2C	rPivot;

	// Get parameters which affect the transformation.
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_POS ))->get_val( i32Time, rPos );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT ))->get_val( i32Time, rPivot );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE ))->get_val( i32Time, rScale );

	// Calculate transformation matrix.
	rPivotMat.set_trans( rPivot );
	rPosMat.set_trans( rPos );
	rScaleMat.set_scale( rScale ) ;
	m_rTM = rPivotMat * rScaleMat * rPosMat;

	//
	// calc bounding box
	//
	float32		f32Width = 25;
	float32		f32Height = 25;
	Vector2C	rMin, rMax;
	Vector2C	rVec;

	m_rVertices[0][0] = -f32Width;		// top-left
	m_rVertices[0][1] = -f32Height;

	m_rVertices[1][0] =  f32Width;		// top-right
	m_rVertices[1][1] = -f32Height;

	m_rVertices[2][0] =  f32Width;		// bottom-right
	m_rVertices[2][1] =  f32Height;

	m_rVertices[3][0] = -f32Width;		// bottom-left
	m_rVertices[3][1] =  f32Height;

	for( i = 0; i < 4; i++ ) {
		rVec = m_rTM * m_rVertices[i];
		m_rVertices[i] = rVec;

		if( !i ) {
			rMin = rMax = rVec;
		}
		else {
			if( rVec[0] < rMin[0] ) rMin[0] = rVec[0];
			if( rVec[1] < rMin[1] ) rMin[1] = rVec[1];
			if( rVec[0] > rMax[0] ) rMax[0] = rVec[0];
			if( rVec[1] > rMax[1] ) rMax[1] = rVec[1];
		}
	}

	// set
	m_rBBox[0] = rMin;
	m_rBBox[1] = rMax;


	float32	f32InputLow = 0.0f;
	float32	f32InputHigh = 1.0f;
	float32	f32InputGamma = 0.0f;
	float32	f32OutputLow = 0.0f;
	float32	f32OutputHigh = 1.0f;

	// Get low
	((ParamFloatC*)m_pInGizmo->get_parameter_by_id( ID_INPUT_LOW ))->get_val( i32Time, f32InputLow );

	// Get high
	((ParamFloatC*)m_pInGizmo->get_parameter_by_id( ID_INPUT_HIGH ))->get_val( i32Time, f32InputHigh );

	// Get gamma
	((ParamFloatC*)m_pInGizmo->get_parameter_by_id( ID_INPUT_GAMMA ))->get_val( i32Time, f32InputGamma );

	// Get low
	((ParamFloatC*)m_pOutGizmo->get_parameter_by_id( ID_OUTPUT_LOW ))->get_val( i32Time, f32OutputLow );

	// Get high
	((ParamFloatC*)m_pOutGizmo->get_parameter_by_id( ID_OUTPUT_HIGH ))->get_val( i32Time, f32OutputHigh );


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

	OpenGLViewportC*	pViewport = (OpenGLViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	if( !pViewport )
		return;

	pViewport->set_ortho_pixel( m_rBBox );


	glDisable( GL_DEPTH_TEST );
	glDepthMask( GL_FALSE );
	glDisable( GL_BLEND );


	// get image from frame buffer
	int32	i32Width = pViewport->get_width();
	int32	i32Height = pViewport->get_height();

	// Trim the bounding box to the layout
	BBox2C	rTrimBBox = pViewport->get_layout().trim( m_rBBox );

	BBox2C	rCapture;
	rCapture[0] = pViewport->layout_to_client( rTrimBBox[0] );
	rCapture[1] = pViewport->layout_to_client( rTrimBBox[1] );

	int32	i32X, i32Y;

	i32X = (int32)floor( rCapture[0][0] );
	i32Y = (int32)floor( rCapture[0][1] );

	int32	i32CaptureWidth = (int32)ceil( rCapture.width() );
	int32	i32CaptureHeight = (int32)ceil( rCapture.height() );

	if( i32X >= i32Width )
		return;
	if( i32X < 0 ) {
		i32CaptureWidth += i32X;
		i32X = 0;
	}
	if( (i32X + i32CaptureWidth) >= i32Width )
		i32CaptureWidth -= (i32X + i32CaptureWidth) - i32Width;
	if( i32CaptureWidth <= 0 )
		return;

	if( i32Y >= i32Height )
		return;
	if( i32Y < 0 ) {
		i32CaptureHeight += i32Y;
		i32Y = 0;
	}
	if( (i32Y + i32CaptureHeight) >= i32Height )
		i32CaptureHeight -= (i32Y + i32CaptureHeight) - i32Height;
	if( i32CaptureHeight <= 0 )
		return;


//	glMatrixMode( GL_COLOR );
//	glLoadIdentity();

//	float	fMat[16];
//	identmat( fMat );

	// hue
//	simplehuerotatemat( fMat, f32Hue );

	// saturation
//	saturatemat( fMat, pow( 1.0f + f32Saturation, 2 ) );
	
	// Lightness
//	offsetmat( fMat, f32Lightness, f32Lightness, f32Lightness );

	
//	glLoadMatrixf( fMat );



	if( g_bColorTableInitialised ) {

		float32	f32Gamma = 1.0f / (float32)pow( 10.0f, f32InputGamma );
		float32	f32Inten;
		uint8	ui8ColorTable[4 * 256];

		if( f32InputHigh < f32InputLow ) {
			f32InputHigh = f32InputLow + 0.01f;
		}

		for( i = 0; i < 256; i++ ) {
			float32	f32A = (float32)i / 255.0f;
			// Determine input intensity
			if( f32InputHigh != f32InputLow )
				f32Inten = (float)(f32A - f32InputLow) / (float)(f32InputHigh - f32InputLow);
			else
				f32Inten = (float)(f32A - f32InputLow);

			f32Inten = (f32Inten < 0.0f ? 0.0f : (f32Inten > 1.0f ? 1.0f : f32Inten));
			if( f32Gamma != 0.0 )
				f32Inten = (float32)pow( f32Inten, 1.0 / f32Gamma );

			// Determine output intensity
			if( f32OutputHigh >= f32OutputLow )
				f32Inten = (float)(f32Inten * (f32OutputHigh - f32OutputLow) + f32OutputLow);
			else if( f32OutputHigh < f32OutputLow )
				f32Inten = (float)(f32OutputLow - f32Inten * (f32OutputLow - f32OutputHigh));

			f32Inten = (f32Inten < 0.0f ? 0.0f : (f32Inten > 1.0f ? 1.0f : f32Inten));

			ui8ColorTable[i * 4 + 0] = (uint8)(f32Inten * 255.0f);
			ui8ColorTable[i * 4 + 1] = (uint8)(f32Inten * 255.0f);
			ui8ColorTable[i * 4 + 2] = (uint8)(f32Inten * 255.0f);
			ui8ColorTable[i * 4 + 3] = (uint8)(f32Inten * 255.0f);
		}

		glEnable( GL_COLOR_TABLE );
		glColorTable( GL_COLOR_TABLE, GL_RGBA, 256, GL_RGBA, GL_UNSIGNED_BYTE, ui8ColorTable );
	}



	glColor4f( 1, 1, 1, 1 );
	glRasterPos2i( i32X, i32Y );
	glCopyPixels( i32X, i32Y, i32CaptureWidth, i32CaptureHeight, GL_COLOR );


	if( g_bColorTableInitialised )
		glDisable( GL_COLOR_TABLE );

//	glMatrixMode( GL_COLOR );
//	glLoadIdentity();
//	glMatrixMode( GL_MODELVIEW );


	glDepthMask( GL_TRUE );
}

BBox2C
LevelsEffectC::get_bbox()
{
	// Return the bounding box.
	return m_rBBox;
}

const Matrix2C&
LevelsEffectC::get_transform_matrix() const
{
	// Return the trnasformation matrix.
	return m_rTM;
}

bool
LevelsEffectC::hit_test( const Vector2C& rPoint )
{
	// Point in polygon test.
	// from c.g.a FAQ
	int		i, j;
	bool	bInside = false;

	for( i = 0, j = 4 - 1; i < 4; j = i++ ) {
		if( ( ((m_rVertices[i][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[j][1])) ||
			((m_rVertices[j][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[i][1])) ) &&
			(rPoint[0] < (m_rVertices[j][0] - m_rVertices[i][0]) * (rPoint[1] - m_rVertices[i][1]) / (m_rVertices[j][1] - m_rVertices[i][1]) + m_rVertices[i][0]) )

			bInside = !bInside;
	}

	return bInside;
}


enum LevelsEffectChunksE {
	CHUNK_LEVELS_BASE =			0x1000,
	CHUNK_LEVELS_TRANSGIZMO =	0x2000,
	CHUNK_LEVELS_INGIZMO =		0x3000,
	CHUNK_LEVELS_OUTGIZMO =		0x4000,
};

const uint32	Levels_VERSION = 1;

uint32
LevelsEffectC::save( SaveC* pSave )
{
	uint32	ui32Error = IO_OK;

	// EffectI base class
	pSave->begin_chunk( CHUNK_LEVELS_BASE, Levels_VERSION );
		ui32Error = EffectI::save( pSave );
	pSave->end_chunk();

	// Transform
	pSave->begin_chunk( CHUNK_LEVELS_TRANSGIZMO, Levels_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_LEVELS_INGIZMO, Levels_VERSION );
		ui32Error = m_pInGizmo->save( pSave );
	pSave->end_chunk();

	pSave->begin_chunk( CHUNK_LEVELS_OUTGIZMO, Levels_VERSION );
		ui32Error = m_pOutGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

uint32
LevelsEffectC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_LEVELS_BASE:
			// EffectI base class
			if( pLoad->get_chunk_version() == Levels_VERSION )
				ui32Error = EffectI::load( pLoad );
			break;

		case CHUNK_LEVELS_TRANSGIZMO:
			// Transform
			if( pLoad->get_chunk_version() == Levels_VERSION )
				ui32Error = m_pTraGizmo->load( pLoad );
			break;

		case CHUNK_LEVELS_INGIZMO:
			// Attribute
			if( pLoad->get_chunk_version() == Levels_VERSION )
				ui32Error = m_pInGizmo->load( pLoad );
			break;

		case CHUNK_LEVELS_OUTGIZMO:
			// Attribute
			if( pLoad->get_chunk_version() == Levels_VERSION )
				ui32Error = m_pOutGizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
