/*
   Project: Adun

   Copyright (C) 2005 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   This application 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 of the License, or (at your option) any later version.

   This application 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
   Library General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/
#include "AdunKernel/AdunGrid.h"

@implementation AdGrid

- (void) _trimGridToCavity
{
	int i,j, k;
	int cavityPoints;
	int* cavityArray;
	AdMatrix* newGrid;

	cavityArray = (int*)malloc(gridPoints*sizeof(int));
	for(cavityPoints=0, i=0; i<gridPoints; i++)
		if([cavity isPointInCavity: grid->matrix[i]])
		{
			cavityArray[cavityPoints] = i;
			cavityPoints++;
		}

	NSDebugLLog(@"AdGrid", @"There are %d points in the cavity", cavityPoints);

	newGrid = [memoryManager allocateMatrixWithRows: cavityPoints withColumns: 3];
	for(i=0; i<cavityPoints; i++)
		for(j=0; j<3; j++)
			newGrid->matrix[i][j] = grid->matrix[cavityArray[i]][j];

	free(cavityArray);
	[memoryManager freeMatrix: grid];
	grid = newGrid;
	gridPoints = cavityPoints;
}

- (void)  _createGrid
{
	int i, j, k, count;
	Vector3D originOffset[3];
	Vector3D* centre;

	for(gridPoints = 1, i=0; i<3; i++)
		gridPoints *= ticksPerAxis[i]; 
	
	NSDebugLLog(@"AdGrid", @"Number of grid points is %d.\n", gridPoints);

	//first we create the grid in the (+,+,+) sector. Then we move it so its
	//centred on the origin
		
	grid = [memoryManager allocateMatrixWithRows: gridPoints
				withColumns: 3];

	NSDebugLLog(@"AdGrid", @"Allocating grid points");

	count = 0;
	for(i=0; i < ticksPerAxis[0]; i++)
		for(j=0; j< ticksPerAxis[1]; j++)
			for(k=0; k < ticksPerAxis[2]; k++)
			{
				grid->matrix[count][0] = i*[[gridSpacing objectAtIndex:0] doubleValue];
				grid->matrix[count][1] = j*[[gridSpacing objectAtIndex:1] doubleValue];
				grid->matrix[count][2] = k*[[gridSpacing objectAtIndex:2] doubleValue];
				count++;
			}

	NSDebugLLog(@"AdGrid", @"Moving grid to origin");
	
	//move to origin	

	for(i=0; i< 3; i++)
		originOffset->vector[i] = -1*((ticksPerAxis[i]-1)*[[gridSpacing objectAtIndex:i] doubleValue])/2;

	[self translateBy: originOffset];

	//now translate everything to the cavity center
	
	centre = [cavity cavityCentre];
	cavityCentre.vector[0] = centre->vector[0];
	cavityCentre.vector[1] = centre->vector[1];
	cavityCentre.vector[2] = centre->vector[2];

	NSDebugLLog(@"AdGrid", @"Moving grid to cavity center");
	[self translateBy: &cavityCentre];

	//trim the grid by removing points not in the cavity

	NSDebugLLog(@"AdGrid", @"Trimming grid to cavity");
	[self _trimGridToCavity];
}

/********************

Object Creation

**********************/

- (void) _cavityInitialisation
{
	int axisLength, i;
	NSEnumerator* extremeEnum;
	id extreme;

 	cavityExtremes = [cavity cavityExtremes];

	NSDebugLLog(@"AdGrid", @"Cavity Extremes %@", cavityExtremes);

	extremeEnum = [cavityExtremes objectEnumerator];
	i = 0;
	while(extreme = [extremeEnum nextObject])
	{
		axisLength = [[extreme objectAtIndex: 0] intValue] - [[extreme objectAtIndex: 1] intValue];
		ticksPerAxis[i] = ceil(axisLength/[[gridSpacing objectAtIndex: i] doubleValue]);
		NSDebugLLog(@"AdGrid", @"There are %d ticks on axis %d", ticksPerAxis[i], i);
		i++;
	}

	NSDebugLLog(@"AdGrid", @"%@ %@", gridSpacing, cavityExtremes);
}

- (id) init
{
	return [self initWithEnvironment: nil];
}

- (id) initWithEnvironment: (id) object
{
	return [self initWithEnvironment: object observe: YES];
}

- (id) initWithEnvironment: (id) object observe: (BOOL) value
{
	if(self = [super initWithEnvironment: object observe: value])
	{
		isTranslationEnabled = YES;
		memoryManager = [AdMemoryManager appMemoryManager];
		grid = NULL;
		if(environment != nil)
		{
			[self synchroniseWithEnvironment];
			[self registerWithEnvironment];
		}
	}

	return self;
}

//Specialised initialisers

+ (id) gridWithDensity: (double) density cavity: (id) aCavity environment: (id) object
{
	id gridObject;

	gridObject = [[AdGrid alloc] initWithDensity: density
			cavity: aCavity
			environment: object];

	return [gridObject autorelease];
}

+ (id) gridWithDivisions: (NSArray*) divisions cavity: (id) aCavity environment: (id) object
{
	id gridObject;

	gridObject = [[AdGrid alloc] initWithDivisions: divisions
			cavity: aCavity
			environment: object];

	return [gridObject autorelease];
}

+ (id) gridWithSpacing: (NSArray*) spacing cavity: (id) aCavity environment: (id) object
{
	id gridObject;

	gridObject = [[AdGrid alloc] initWithSpacing: spacing
			cavity: aCavity
			environment: object];

	return [gridObject autorelease];
}

- (id) initWithDensity: (double) density cavity: (id) cavity environment: (id) object
{
	NSWarnLog(@"Method %@ not implemented", NSStringFromSelector(_cmd));
	return nil;
}

- (id) initWithDivisions: (NSArray*) divisions cavity: (id) cavity environment: (id) object
{
	NSWarnLog(@"Method %@ not implemented", NSStringFromSelector(_cmd));
	return nil;
}

- (id) initWithSpacing: (NSArray*) spacing cavity: aCavity environment: (id) object
{
	[self initWithEnvironment: object];
	gridSpacing = [[spacing copy] retain];
	[self setCavity: aCavity];

	return self;
}

- (void) dealloc
{
	[gridSpacing release];
	[memoryManager freeMatrix: grid];
}

/*
 * Environment observation
 */

- (void) updateForKey: (NSString*) key value: (id) value object: (id) object
{
	//no updates as of yet
}

- (void) registerWithEnvironment
{
	//nothing to register
}

- (void) deregisterWithEnvironment
{
	//nothing to deregister
}

- (void) synchroniseWithEnvironment
{
	//nothing to retrieve
}

- (void) setEnvironment: (id) object
{
	[self deregisterWithEnvironment];
	object = environment;
	[self registerWithEnvironment];
}


/*******************

Public Methods

********************/

- (void) translateBy: (Vector3D*) translationVector
{
	int i, k;

	if(isTranslationEnabled)
		for(i=0; i<gridPoints;i++)
			for(k=0; k<3; k++)
				grid->matrix[i][k] += translationVector->vector[k];
}

- (void) setTranslationEnabled: (BOOL) value
{
	isTranslationEnabled = value;
}

- (BOOL) isTranslationEnabled
{
	return isTranslationEnabled;
}

- (void) cavityDidMove
{
	int i;
	Vector3D translation, *newCentre;	

	newCentre = [cavity cavityCentre];
	for(i=0; i<3; i++)
	{
		translation.vector[i] = newCentre->vector[i] - cavityCentre.vector[i];
		cavityCentre.vector[i] = newCentre->vector[i];
	}

	[self translateBy: &translation];
}

- (void) resetCavity
{
	//free the last grid
	
	if(grid != NULL)
		[memoryManager freeMatrix: grid];

	[self _cavityInitialisation];
	[self _createGrid];
}

//Setting the cavity. The cavity is an object that conforms to the
//AdGridDelegate protocol. It is not retained. 

- (void) setCavity: (id) anObject
{
	if([anObject conformsToProtocol: @protocol(AdGridDelegate)])
		cavity = anObject;
	else
		[NSException raise: NSInvalidArgumentException
			format: @"Delegate does not conform to AdGridDelegate protocol"];

	//free the last grid
	
	if(grid != NULL)
		[memoryManager freeMatrix: grid];

	[self _cavityInitialisation];
	[self _createGrid];
}


/*************

Accessors

**************/

//The grids delegate object

- (id) cavity
{
	return cavity;
}

- (id) grid
{
	return [NSValue valueWithPointer: grid];
}

- (NSArray*) spacing
{
	return [gridSpacing copy];
}

- (NSArray*) divisions
{
	NSWarnLog(@"Method %@ not implemented", NSStringFromSelector(_cmd));
	return nil;
}

- (int) numberOfPoints
{
	return grid->no_rows;
}

/****************

Coding

*****************/

- (id) initWithCoder: (NSCoder*) decoder
{
	int i, j, count;
	int rows, length;	
	double *buffer;
	Vector3D* centre;

	self = [super initWithCoder: decoder];	
	if([decoder allowsKeyedCoding])
	{	
		memoryManager = [AdMemoryManager appMemoryManager];
		rows = [decoder decodeIntForKey: @"Grid.Rows"];
		grid = [memoryManager allocateMatrixWithRows: rows withColumns: 3];
		cavity = [decoder decodeObjectForKey: @"Cavity"];
		isTranslationEnabled = [decoder decodeBoolForKey: @"TranslationEnabled"];
		gridSpacing = [[decoder decodeObjectForKey: @"GridSpacing"] retain];
		buffer = (double*)[decoder decodeBytesForKey: @"GridMatrix" returnedLength: &length];
		if(length != (int)sizeof(AdMatrixSize)*grid->no_rows*grid->no_columns)
			[NSException raise: NSInternalInconsistencyException
				format: @"Expected grid size and decoded grid size not equal"];

		for(count= 0, i=0; i< grid->no_rows; i++)
			for(j=0; j<grid->no_columns; j++)
			{
				grid->matrix[i][j] = buffer[count];
				count++;
			}

		gridPoints = grid->no_rows;
		//recalculate ticksPerAxis and cavityExtremes
		[self _cavityInitialisation];
		centre = [cavity cavityCentre];
		for(i=0; i<3; i++)
			cavityCentre.vector[i] = centre->vector[i];

		environment = [AdEnvironment globalEnvironment];
		if(environment != nil)
		{
			[self synchroniseWithEnvironment];
			[self registerWithEnvironment];
		}
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];

	return self;
}

- (void) encodeWithCoder: (NSCoder*) encoder
{
	int bytesLength;

	[super encodeWithCoder: encoder];
	if([encoder allowsKeyedCoding])
	{
		bytesLength = sizeof(AdMatrixSize)*grid->no_rows*grid->no_columns;	
		[encoder encodeBytes: (uint8_t*)grid->matrix[0] length: bytesLength forKey: @"GridMatrix"];
		[encoder encodeInt: grid->no_rows forKey: @"Grid.Rows"];		
		[encoder encodeConditionalObject: cavity forKey: @"Cavity"];
		[encoder encodeBool: isTranslationEnabled forKey: @"TranslationEnabled"];
		[encoder encodeObject: gridSpacing forKey: @"GridSpacing"];
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"%@ class does not support non keyed coding", [self class]];
}

@end
