/*
   Project: Adun

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

   Author: Michael Johnston

   Created: 2005-06-02 15:34:11 +0200 by 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/AdunDataMatrix.h"

@implementation AdDataMatrix

+ (AdDataMatrix*) matrixFromGSLVector: (gsl_vector*) aVector
{
	int i, j;
	AdDataMatrix* newMatrix;

	newMatrix = [[AdDataMatrix alloc] initWithRows: aVector->size
			columns: 1];

	for(i=0; i<(int)aVector->size; i++)
		for(j=0; j<1; j++)
			[newMatrix setElementAtRow: i
				column: j
				withValue: [NSNumber 
					numberWithDouble: gsl_vector_get(aVector, i)]];

	return [newMatrix autorelease];
}

+ (AdDataMatrix*) matrixFromGSLMatrix: (gsl_matrix*) aMatrix
{
	int i, j;
	AdDataMatrix* newMatrix;

	newMatrix = [[AdDataMatrix alloc] initWithRows: aMatrix->size1
			columns: aMatrix->size2];

	for(i=0; i<(int)aMatrix->size1; i++)
		for(j=0; j<(int)aMatrix->size2; j++)
			[newMatrix setElementAtRow: i
				column: j
				withValue: [NSNumber 
					numberWithDouble: gsl_matrix_get(aMatrix, i, j)]];

	return [newMatrix autorelease];
}

+ (AdDataMatrix*) matrixFromADMatrix: (AdMatrix*) aMatrix
{
	int i, j;
	AdDataMatrix* newMatrix;

	newMatrix = [[AdDataMatrix alloc] initWithRows: aMatrix->no_rows
			columns: aMatrix->no_columns];

	for(i=0; i<aMatrix->no_rows; i++)
		for(j=0; j<aMatrix->no_columns; j++)
			[newMatrix setElementAtRow: i
				column: j
				withValue: [NSNumber 
					numberWithDouble: aMatrix->matrix[i][j]]];

	return [newMatrix autorelease];
}

- (AdDataMatrix*) initWithRows: (int) rowValue columns: (int) columnValue;
{
	int i, j;
	NSMutableArray* row;

	if(self = [super init])
	{
		numberOfRows = rowValue;
		numberOfColumns = columnValue;

		matrix = [NSMutableArray new];

		for(i=0; i<numberOfRows; i++)
		{
			row = [NSMutableArray array];
			for(j=0; j<numberOfColumns; j++)
				[row addObject: [NSNumber numberWithDouble: 0.0]];

			[matrix addObject: row];
		}
		columnHeaders = nil;
		name = [@"None" retain];
	}

	return self;
}

- (id) init
{
	return [self initWithRows: 0 columns: 0];
}

- (void) dealloc
{
	[matrix release];
	[columnHeaders release];
	[name release];
	[super dealloc];
}


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

Setting Elements

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

- (void) setElementAtRow: (int) rowIndex 
	column: (int) columnIndex 
	withValue: (NSNumber*) value;
{
	[[matrix objectAtIndex: rowIndex] replaceObjectAtIndex: columnIndex
		withObject: value];
}

- (NSNumber*) elementAtRow: (int) rowIndex
	 column: (int) columnIndex;
{
	return [[matrix objectAtIndex: rowIndex] objectAtIndex: columnIndex];
}

- (NSArray*) matrixRows
{
	return matrix;
}

- (void) setMatrixRows: (NSMutableArray*) rowArray
{
	[matrix release];
	matrix = [rowArray retain];
}

- (void) extendMatrixWithRow: (NSArray*) anArray
{
	//have to copy the numberOfRows elements into the matrix
	//since who knows whats going to happen to the array later
	//(it may be mutable)	

	if((int)[anArray count] == numberOfColumns)
	{	
		[matrix addObject: [NSMutableArray arrayWithArray: anArray]];
		numberOfRows++;
	}
	else if(numberOfColumns == 0)	
	{
		numberOfColumns = [anArray count];
		[matrix addObject: [NSMutableArray arrayWithArray: anArray]];
		numberOfRows++;
	}
	else
	{
		[NSException raise: NSInvalidArgumentException
			format: @"Incorrect number of elements in array (%d, %d)",
			[anArray count], numberOfColumns];	
	}
}	

- (void) extendMatrixWithColumn: (NSArray*) anArray
{
	int i;

	//check if its the right size

	if((int)[anArray count] != numberOfRows)
		[NSException raise: NSInvalidArgumentException
			format: @"Incorrect number of elements in array (%d, %d)",
			[anArray count], numberOfRows];	

	for(i=0; i<numberOfRows; i++)
		[[matrix objectAtIndex: i] addObject: 
				[[anArray objectAtIndex: i] copy]];

	numberOfColumns++; 

	//expand columnHeaders if they exist

	if(columnHeaders != nil)
		[columnHeaders addObject: @""];
}


- (void) removeRowsWithIndexes: (NSIndexSet*) indexSet
{
	int* buffer;

	//maybe one day this will be implemented - only in Cocoa for now
	//[matrix removeObjectsWithIndexes: indexSet];

	buffer = (int*)malloc([indexSet count]*sizeof(int));
	[indexSet getIndexes: buffer 
			maxCount: [indexSet count]
			inIndexRange: NULL];
	[matrix	removeObjectsFromIndices: buffer numIndices: [indexSet count]];
	free(buffer); 

	numberOfRows = [matrix count];
}

/**
\todo Add validation
\note maybe we should copy this matrix?
*/

- (void) extendMatrixWithMatrix: (AdDataMatrix*) extendingMatrix
{
	NSEnumerator* rowEnum;
	id row;

	//if we are adding this to an empty matrix numberOfColumns will be 0
	//need to give it the correct value

	if(numberOfColumns == 0)
		numberOfColumns = [extendingMatrix numberOfColumns];

	rowEnum = [[extendingMatrix matrixRows] objectEnumerator];

	while(row = [rowEnum nextObject])
		[matrix addObject: [NSMutableArray arrayWithArray: row]];
	
	numberOfRows += [extendingMatrix numberOfRows];
}

- (void) printMatrix
{
	int i;

	for(i=0; i<numberOfRows; i++)
		NSLog(@"%@\n", [matrix objectAtIndex: i]);
}		

- (BOOL) writeMatrixToFile: (NSString*) filename
{
	int i;
	FILE* file_p;

	file_p = fopen([filename cString], "w");
	
	if(file_p == NULL)
		return NO;

	if(columnHeaders != nil)
		GSPrintf(file_p, @"%@\n", 
			[columnHeaders componentsJoinedByString: @" "]);

	for(i=0;i<numberOfRows; i++)
		GSPrintf(file_p, @"%@\n", 
			[[matrix objectAtIndex: i] 
			componentsJoinedByString: @" "]);

	fclose(file_p);

	return YES;
}

- (NSMutableArray*) column: (int) columnIndex
{
	int i;
	NSMutableArray* columnCopy;

	columnCopy = [NSMutableArray arrayWithCapacity:1];

	for(i = 0; i<numberOfRows; i++)
		[columnCopy addObject: 
			[[[matrix objectAtIndex: i] 
			objectAtIndex: columnIndex] copy]];

	return columnCopy;
}

- (int) numberOfRows
{
	return numberOfRows;
}

- (int) numberOfColumns
{
	return numberOfColumns;
}

/*******
NSCoding Methods
********/

- (id) initWithCoder: (NSCoder*) decoder
{	
	int i,j;
	int matrixElements;
	int length, count;
	double *matrixStore;
	NSMutableArray* matrixRow;
	NSNumber *element, *number;

	if([decoder allowsKeyedCoding])
	{
		matrix = [NSMutableArray new];
		numberOfRows = [decoder decodeIntForKey: @"Rows"];
		numberOfColumns = [decoder decodeIntForKey: @"Columns"];
		matrixStore = (double*)[decoder decodeBytesForKey: @"Matrix"
					returnedLength: &length];
		matrixElements = length/sizeof(double);
		for(i=0, count=0; i<numberOfRows; i++)
		{
			matrixRow = [NSMutableArray arrayWithCapacity: 1];
			for(j=0; j<numberOfColumns; j++)
			{
				element = [NSNumber numberWithDouble: matrixStore[count]];
				[matrixRow addObject: element];
				count++;
			}
			[matrix addObject: matrixRow];
		}

		columnHeaders = [decoder decodeObjectForKey: @"ColumnHeaders"];
		[columnHeaders retain];
		name = [decoder decodeObjectForKey: @"Name"];
		[name retain];
	}
	else
	{	
		matrix = [NSMutableArray new];
		matrixStore = (double*)[decoder decodeBytesWithReturnedLength: &length];
		number = [decoder decodeObject];
		numberOfRows = [number intValue];
		number = [decoder decodeObject];
		numberOfColumns = [number intValue];
		matrixElements = length/sizeof(double);
		for(i=0, count=0; i<numberOfRows; i++)
		{
			matrixRow = [NSMutableArray arrayWithCapacity: 1];
			for(j=0; j<numberOfColumns; j++)
			{
				element = [NSNumber numberWithDouble: matrixStore[count]];
				[matrixRow addObject: element];
				count++;
			}
			[matrix addObject: matrixRow];
		}

		columnHeaders = [decoder decodeObject];
		if([columnHeaders isEqual:@"NoHeaders"])
			columnHeaders = nil;
		else	
			[columnHeaders retain];
			
		name = [decoder decodeObject];
		[name retain];
	}

	return self;
}

//probably should encode types of each number as well

- (void) encodeWithCoder: (NSCoder*) encoder
{
	int i, j, bytesLength, count;
	double* matrixStore;
	id array;

	if([encoder allowsKeyedCoding])
	{
		//FIXME: Assuming the matrix only stores numbers.
		bytesLength = numberOfRows*numberOfColumns*sizeof(double);
		matrixStore = (double*)malloc(numberOfRows*numberOfColumns*sizeof(double));
	
		for(i=0, count=0; i<numberOfRows; i++)
			for(j=0; j<numberOfColumns; j++)
			{
				matrixStore[count] = [[[matrix objectAtIndex: i] objectAtIndex: j] doubleValue]; 
				count++;
			}
		
		[encoder encodeBytes: (uint8_t*)matrixStore length: bytesLength forKey: @"Matrix"];
		[encoder encodeInt: numberOfRows forKey: @"Rows"];
		[encoder encodeInt: numberOfColumns forKey: @"Columns"];

		if(columnHeaders != nil)
		{
			NSLog(@"Encoding headers %@", columnHeaders);
			[encoder encodeObject: columnHeaders forKey: @"ColumnHeaders"];
		}	

		if(name != nil)
		{
			[encoder encodeObject: name forKey: @"Name"];
			NSLog(@"Encoding name %@", name);
		}	

		free(matrixStore);
	}
	else
	{
		bytesLength = numberOfRows*numberOfColumns*sizeof(double);
		matrixStore = (double*)malloc(numberOfRows*numberOfColumns*sizeof(double));
	
		for(i=0, count=0; i<numberOfRows; i++)
			for(j=0; j<numberOfColumns; j++)
			{
				matrixStore[count] = [[[matrix objectAtIndex: i] objectAtIndex: j] doubleValue]; 
				count++;
			}
		
		[encoder encodeBytes: (uint8_t*)matrixStore length: bytesLength];
		[encoder encodeObject: [NSNumber numberWithInt: numberOfRows]];
		[encoder encodeObject: [NSNumber numberWithInt: numberOfColumns]];

		//[encoder encodeObject: matrix];
		if(columnHeaders != nil)
			[encoder encodeObject: columnHeaders];
		else
			[encoder encodeObject: @"NoHeaders"];
			
		[encoder encodeObject: name];
	}
}

- (void) setColumnHeaders: (NSArray*) anArray
{
	id holder;

	if((int)[anArray count] != numberOfColumns)
		[NSException raise: NSInvalidArgumentException
			format: @"Incorrect number of elements in array (%d, %d)",
			[anArray count], numberOfRows];	

	if(anArray != columnHeaders)
	{
		holder = columnHeaders;
		columnHeaders = [anArray mutableCopy];
		[holder release];
	}	
}

- (NSArray*) columnHeaders
{
	return columnHeaders;
}

- (void) setName: (NSString*) aString
{
	id holder;

	holder = name;
	name = [aString retain];
	[holder release];
}

- (NSString*) name
{
	return name;
}

@end
