#import "PLTMatrix.h"

@implementation PLTMatrix

- (id) init
{
	[super init];
	
	_rows = 0;
	_cols = 0;
	
	_rowExtension = 0;
	_colExtension = 0;
//	_format = @"%12.5f";
	_format = @"%g";
	_columnNames = nil;
	
	_values = nil;
	
	return self;
}

- (id) initWithRows: (int) rows andCols: (int) cols
{
	[self init];
	
	_rows = rows;
	_cols = cols;
	
	_rowExtension = rows;
	_colExtension = cols;
	
//	_format = @"%12.5f";
	_values = [[NSMutableData alloc] initWithLength:(unsigned)rows*cols*sizeof(double)];
	_columnNames = nil;
	
	return self;
}

- (id) initWithString:(NSString *)fileContents withHeaders:(BOOL)headers
{
	int firstRow;
	if (headers) {
		firstRow = 1;
	} else {
		firstRow = 0;
	}

	NSArray *array = [fileContents componentsSeparatedByString:@"\n"];
	
	NSArray *headerTitles;
	if (headers) {
		headerTitles = [[array objectAtIndex:0] componentsSeparatedByString:@" "];
	}
	
	int i = firstRow;
	NSString *line = nil;
	while(i < [array count]) {
		line = [[array objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		if (([line length]>0) && ([line characterAtIndex:0] != (unichar)'!')) {
			break;
		}
		i++;
	} 

	NSScanner *scanner = [[NSScanner alloc] initWithString:line];
	int nColumns = 0;
	while (![scanner isAtEnd]) {
		double value;
		if ([scanner scanDouble:&value]) {
			nColumns++;
		}
	}
	[scanner release];
	
	int nRows = 0;
	for(i=firstRow; i<[array count]; i++) {
		line = [[array objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		if (([line length]>0) && ([line characterAtIndex:0] != (unichar)'!')) {
			nRows++;
		}
	}
	
	[self initWithRows:nRows andCols:nColumns];
	
	if (headers) {
		[self setColumnHeadings:headerTitles];
	} else {
		NSMutableArray *headers = [[NSMutableArray alloc] init];
		int i;
		for(i=0; i<nColumns; i++) {
			NSString *header = [[NSString alloc] initWithFormat:@"Column %d", i];
			[headers addObject:header];
			[header release];
		}
		[self setColumnHeadings:headers];
		[headers release];
	}
	
	int row = 0;
	for(i=firstRow; i<[array count]; i++) {
		NSString *line = [[array objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		if (([line length]>0) && ([line characterAtIndex:0] != (unichar)'!')) {
			NSScanner *scanner = [[NSScanner alloc] initWithString:line];
			
			int j = 0;
			while ((![scanner isAtEnd]) && (j<nColumns)) {
				double value;
				if([scanner scanDouble:&value]) {
					[self setCell:value atRow:row andCol:j];
					j++;
				}
			}
			row++;
			[scanner release];
		}
	}
	
//	[fileContents release];
	
	if (headers) {
		[headerTitles release];
	}
	
	return self;
}

- (id) initWithContentsOfFile: (NSString *)path withHeaders:(BOOL)headers
{
	int firstRow;
	if (headers) {
		firstRow = 1;
	} else {
		firstRow = 0;
	}

	NSString *fileName = [path stringByStandardizingPath];
#ifdef GNUSTEP
	NSString *fileContents = [[NSString alloc] initWithContentsOfFile:fileName];
#else
	NSString *fileContents = [[NSString alloc] initWithContentsOfFile:fileName encoding:NSASCIIStringEncoding error:nil];
#endif
	
	NSArray *array = [fileContents componentsSeparatedByString:@"\n"];
	
	NSArray *headerTitles;
	if (headers) {
		headerTitles = [[array objectAtIndex:0] componentsSeparatedByString:@" "];
	}
	
	int i = firstRow;
	NSString *line = nil;
	while(i < [array count]) {
		line = [[array objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		if (([line length]>0) && ([line characterAtIndex:0] != (unichar)'!')) {
			break;
		}
		i++;
	} 

	NSScanner *scanner = [[NSScanner alloc] initWithString:line];
	int nColumns = 0;
	while (![scanner isAtEnd]) {
		double value;
		if ([scanner scanDouble:&value]) {
			nColumns++;
		}
	}
	[scanner release];
	
	int nRows = 0;
	for(i=firstRow; i<[array count]; i++) {
		line = [[array objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		if (([line length]>0) && ([line characterAtIndex:0] != (unichar)'!')) {
			nRows++;
		}
	}
	
	[self initWithRows:nRows andCols:nColumns];
	
	if (headers) {
		[self setColumnHeadings:headerTitles];
	} else {
		NSMutableArray *headers = [[NSMutableArray alloc] init];
		int i;
		for(i=0; i<nColumns; i++) {
			NSString *header = [[NSString alloc] initWithFormat:@"Column %d", i];
			[headers addObject:header];
			[header release];
		}
		[self setColumnHeadings:headers];
		[headers release];
	}
	
	int row = 0;
	for(i=firstRow; i<[array count]; i++) {
		NSString *line = [[array objectAtIndex:i] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
		if (([line length]>0) && ([line characterAtIndex:0] != (unichar)'!')) {
			NSScanner *scanner = [[NSScanner alloc] initWithString:line];
			
			int j = 0;
			while ((![scanner isAtEnd]) && (j<nColumns)) {
				double value;
				if([scanner scanDouble:&value]) {
					[self setCell:value atRow:row andCol:j];
					j++;
				}
			}
			row++;
			[scanner release];
		}
	}
	
	[fileContents release];
	
	if (headers) {
		[headerTitles release];
	}
	
	return self;
}

- (id) initSubMatrix:(PLTMatrix *)matrix firstRow:(int)firstRow lastRow:(int)lastRow firstCol:(int)firstCol lastCol:(int)lastCol
{
	int nRows = lastRow - firstRow + 1;
	int nColumns = lastCol - firstCol + 1;
	
	[self initWithRows:nRows andCols:nColumns];
	
	int i;
	for(i=firstRow; i<=lastRow; i++) {
		int j;
		for(j=firstCol; j<=lastCol; j++) {
			double value = [matrix cellAtRow:i andCol:j];
			[self setCell:value atRow:(i - firstRow) andCol:(j - firstCol)];
		}
	}
	
	NSMutableArray *hdgs = [[NSMutableArray alloc] init];
	
	NSArray *matrixHeadings = [matrix columnHeadings];
	if (matrixHeadings!=nil) {
		int j;
		for(j=firstCol; j<=lastCol; j++) {
			[hdgs addObject:[matrixHeadings objectAtIndex:j]];
		}
	}
	
	[self setColumnHeadings:hdgs];
	[hdgs release];
	
	return self;
}

- (id) initWithMatrix:(PLTMatrix *)matrix
{
	int rows = [matrix rows];
	int cols = [matrix cols];
	[self initSubMatrix:matrix firstRow:0 lastRow:(rows - 1) firstCol:0 lastCol:(cols - 1)];
	
	return self;
}

- (id) initWithHorizontalConcatenationOf:(PLTMatrix *)left and:(PLTMatrix *)right
{
	int leftRows = [left rows];
	int rightRows = [right rows];
	int leftCols = [left cols];
	int rightCols = [right cols];
	
	if (leftRows!=rightRows) {
		return nil;
	}
	
	int rows = leftRows;
	int cols = leftCols + rightCols;
	
	[self initWithRows:rows andCols:cols];
	
	int i;
	for(i=0; i<rows; i++) {
		int j;
		for(j=0; j<leftCols; j++) {
			[self setCell:[left cellAtRow:i andCol:j] atRow:i andCol:j];
		}
		for(j=0; j<rightCols; j++) {
			[self setCell:[right cellAtRow:i andCol:j] atRow:i andCol:(leftCols + j)];
		}
	}
	
	NSArray *leftHeadings = [left columnHeadings];
	NSArray *rightHeadings = [right columnHeadings];
	[self  setColumnHeadings:[leftHeadings arrayByAddingObjectsFromArray:rightHeadings]];
	
	return self;
}

- (void) setValues:(NSMutableData *)values
{
	[values retain];
	[_values release];
	_values = values;
}

- (NSMutableData *) values
{
	return _values;
}
 
- (void) setRows:(int) rows
{
	_rows = rows;
}

- (int) rows
{
	return _rows;
}

- (int) cols
{
	return _cols;
}

- (void) setCols:(int) cols
{
	_cols = cols;
}

- (int) size
{
	return _rows*_cols;
}

- (void) setRowExtension:(int) rowExtension
{
	_rowExtension = rowExtension;
}

- (int) rowExtension
{
	return _rowExtension;
}

- (void) setColExtension:(int) colExtension
{
	_colExtension = colExtension;
}

- (int) colExtension
{
	return _colExtension;
}

- (void) setCell: (double) value atRow: (int) row andCol: (int) col
{
	if ((row<0) || (col<0)) {
		return;
	}
	
	if (row>=[self rows]) {
		if (_rowExtension<=0) {
			return;
		}
	
		int newRows = [self rows] + _rowExtension;
		
		_rows = newRows; 
		[_values setLength:(unsigned)newRows*[self cols]*sizeof(double)];
	}
	
	if (col>=_cols) {
		return;
	}
	
	double *bytes = [_values mutableBytes];
	bytes[row*_cols + col] = value;


}

- (double) cellAtRow: (int) row andCol: (int) col
{
	if ((row>_rows) || (col>_cols)) {
		return 0.0;
	}
	
	double *bytes = [_values mutableBytes];
	return bytes[row*_cols + col];
}

- (void) minAndMaxValues:(double *)minValue and:(double *) maxValue
{
	double ymin = HUGE_VAL;
	double ymax = -HUGE_VAL;
	int i;
	for(i=0; i<[self rows]; i++) {
		int j;
		for(j=0; j<[self cols]; j++) {
			double y = [self cellAtRow:i andCol:j];
			if (y>ymax) ymax = y;
			if (y<ymin) ymin = y;
		}
	}
	*minValue = ymin;
	*maxValue = ymax;
}

- (void) setPrintFormat: (NSString *) format
{
	[format retain];
	[_format release];
	_format = format;
}

- (void) setColumnHeadings:(id)headings
{
//	[_columnNames release];
//	[_columnNames initWithArray:headings];
	[headings retain];
	[_columnNames release];
	_columnNames = headings;
}

- (NSArray *)columnHeadings
{
	return _columnNames;
}

- (NSString *)columnHeadingForColumn:(int)col
{
	return [_columnNames objectAtIndex:col];
}

- (void) setColumnHeading:(NSString *)columnHeading forColumn:(int)col
{
	[_columnNames replaceObjectAtIndex:col withObject:columnHeading];
}

- (NSString *) format
{
	return _format;
}

- (void) print
{
	int i;
	int j;
	
	for(i=0; i<_rows; i++) {
		printf("%4d ", i);
		for(j=0; j<_cols; j++) {
			printf([_format UTF8String], [self cellAtRow:i andCol:j]);
		}
		printf("\n");
	} 
} 

- (NSString *) stringRepresentationUsingType:(NSString *)type outputPoints:(int)numPoints
{
	if ([type caseInsensitiveCompare:@"html"]==NSOrderedSame) {
		return [self HTMLStringRepresentation:numPoints];
	} else if ([type caseInsensitiveCompare:@"txt"]==NSOrderedSame || [type caseInsensitiveCompare:@"lst"]==NSOrderedSame) {
		return [self delimitedStringRepresentation:@" " outputPoints:numPoints];
	} else if ([type caseInsensitiveCompare:@"xml"]==NSOrderedSame) {
		return [self XMLStringRepresentation:numPoints];
	} else {
		return nil;
	}
}

- (NSString *) delimitedStringRepresentation:(NSString *)delim outputPoints:(int)numPoints
{
	NSMutableString *delimitedString = [NSMutableString string];
	
	int i;
	int j;
	
	int rows = ((numPoints<0) || (numPoints>=[self rows]))?[self rows]:numPoints;
	
	for(i=0; i<rows; i++) {
		for(j=0; j<[self cols]; j++) {
			if (j>0) {
				[delimitedString appendString:delim];
			}
			[delimitedString appendFormat:_format, [self cellAtRow:i andCol:j]];
		}
		[delimitedString appendString:@"\n"];
	} 

	return delimitedString;
}

- (NSString *) XMLStringRepresentation:(int)numPoints
{
	int rows = ((numPoints<0) || (numPoints>=[self rows]))?[self rows]:numPoints;
	
	NSMutableString *xmlString = [NSMutableString string];

	[xmlString  appendString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"];
	[xmlString  appendString:@"<xmldata>\n"];
	NSString *formatString = [[NSString alloc] initWithFormat:@"<cell>%@</cell>", [self format]];
	[xmlString  appendString:@"<heading>\n"];
	int i;
	for(i=0; i<[self cols]; i++) {
		[xmlString  appendFormat:@"<cell>%@</cell>", [[self columnHeadings] objectAtIndex:i]];
	}
	[xmlString  appendString:@"</heading>\n"];
	for(i=0; i<rows; i++) {
		[xmlString  appendString:@"<row>\n"];
		int j;
		for(j=0; j<[self cols]; j++) {
			[xmlString  appendFormat:formatString, [self cellAtRow:i andCol:j]];
		}
		[xmlString  appendString:@"\n</row>\n"];
	}
	[xmlString  appendString:@"</xmldata>\n"];
	[formatString release];

	return xmlString;
}

- (NSString *) HTMLStringRepresentation:(int)numPoints
{
	int rows = ((numPoints<0) || (numPoints>=[self rows]))?[self rows]:numPoints;
	
	NSMutableString *htmlString = [NSMutableString string];
	
	[htmlString  appendString:@"<html lang=\"en\">\n"];
	[htmlString  appendString:@"<head><title> PLTMatrix </title></head>\n"];

	[htmlString  appendString:@"<body>\n"];
	
	[htmlString  appendString:@"<table border=1>\n"];
	int i;
	[htmlString  appendString:@"<tr>\n"];
	for(i=0; i<[self cols]; i++) {
		[htmlString  appendFormat:@"<td>%@</td>\n", [[self columnHeadings] objectAtIndex:i]];
	}
	[htmlString  appendString:@"</tr>\n"];
	
	NSString *formatString = [[NSString alloc] initWithFormat:@"<td>%@</td>\n", [self format]];
	for(i=0; i<rows; i++) {
		[htmlString  appendString:@"<tr>\n"];
		int j;
		for(j=0; j<[self cols]; j++) {
			[htmlString  appendFormat:formatString, [self cellAtRow:i andCol:j]];
		}
		[htmlString  appendString:@"</tr>\n"];
	}
	[formatString release];
	
	[htmlString  appendString:@"</table>\n"];
	
	[htmlString  appendString:@"</body>\n"];
	[htmlString  appendString:@"</html>\n"];
	return htmlString;
}

- (void) dealloc
{
	[_values release];
	[_format release];
	[super dealloc];
}

@end