// ToolBuilder.m
// By Charles G. Fleming, Educational Computing Services, Allegheny College.
// Copyright 1993, Allegheny College.
// You may freely copy, distribute and reuse this code. 
// Allegheny College and the author disclaim any warranty of any kind, 
// expressed or implied, as to its fitness for any particular use.

#import "ToolBuilder.h"
#import "SaverAndRetriever.h"
#import "NibSaver.h"

#import <ansi/ctype.h>

@implementation ToolBuilder

- appDidInit:sender
{
	NXBundle *mainBundle;
	char bundleDirectory[MAXPATHLEN+1];
	
	mainBundle = [NXBundle mainBundle];	
	strcpy(bundleDirectory, [mainBundle directory]);

	[(FileRetriever *)connectorFileRetriever setDirectory:bundleDirectory];
	[(FileRetriever *)connectorFileRetriever setFilename:"connectorList"];
	[connectorFileRetriever open];
	
	[self makeKeyAndOrderFront:self];
	return self;
}

// Start with an empty matrix.
- setConnectorMatrix:anObject
{
	connectorMatrix = anObject;

	[connectorMatrix removeRowAt:0 andFree:YES];
	[connectorMatrix removeRowAt:0 andFree:YES];
	return self;
}
		
- setClassnameTextField:anObject
{
	NXRect textRect = {0, 0, 1E38, 1E38}, textFieldRect = {0, 0, 315, 44};
	id accessoryView;
	Font *font;

	classnameTextField = anObject;

	scratchText = [[Text allocFromZone:[self zone]] initFrame:&textRect text:""
			alignment:NX_LEFTALIGNED];
	[scratchText setMonoFont:YES];
	[scratchText setFont:[Font newFont:"Courier" size:14]];

	// Get the open panel.  We'll use it to select the directory where the
	// files created will be stored.
	openPanel = [OpenPanel new];
	[openPanel chooseDirectories:YES];

	accessoryView = [[TextField allocFromZone:[self zone]]
			initFrame:&textFieldRect];

	font = [Font newFont:"Helvetica" size:16];
	[[accessoryView cell] setFont:font];
	[accessoryView setStringValue:"Select a directory where the class "
			"files should be stored."];
	[accessoryView setAlignment:NX_CENTERED];
	[accessoryView setBackgroundGray:NX_LTGRAY];
	[accessoryView setBezeled:NO];
	[accessoryView setSelectable:NO];
	[openPanel setAccessoryView:accessoryView];
	return self;
}

- createClassFiles:sender
{
	NXBundle *mainBundle;
	const char *classname;
	char *classnameM, *classnameH, *classnameInspectorM, *classnameInspectorH;
	char inspectorPath[MAXPATHLEN+1], inspectorNibDirectory[MAXPATHLEN+1];
	int length;

	// Get the name of the class.
	classname = [classnameTextField stringValue];
	if(length = strlen(classname))
	{

		[self constructSetMethodStrings];

		// Select the directory where the class files will be stored.
		if([openPanel runModal])
		{
			[self makeFirstResponder:self];

			strcpy(resultsDirectory, [(OpenPanel *)openPanel filename]);

			[(FileSaver*)fileSaver setDirectory:resultsDirectory];

			length = length+3;

			classnameM = (char *)malloc(length);
			classnameH = (char *)malloc(length);
			sprintf(classnameM, "%s.m", classname);
			sprintf(classnameH, "%s.h", classname);

			[self convertFile:"ToolTemplate" ext:"m" destination:classnameM];
			[self convertFile:"ToolTemplate" ext:"h" destination:classnameH];

			free(classnameM);
			free(classnameH);

			if([createInspectorButton state])
			{	
				classnameInspectorM = (char *)malloc(length + 10);
				classnameInspectorH = (char *)malloc(length + 10); 
				sprintf(classnameInspectorM, "%sInspector.m", classname);
				sprintf(classnameInspectorH, "%sInspector.h", classname);

				[self convertFile:"ToolInspectorTemplate" ext:"m"
						destination:classnameInspectorM];
				[self convertFile:"ToolInspectorTemplate" ext:"h"
						destination:classnameInspectorH];
				free(classnameInspectorM);
				free(classnameInspectorH);

				// Create a directory for the inspector's nib file.
				sprintf(inspectorNibDirectory, "%s/%sInspector.nib",
						resultsDirectory, classname);
				mkdir(inspectorNibDirectory, 00755);

				// Get the path to the .nib template.
				mainBundle = [NXBundle mainBundle];

				[mainBundle getPath:inspectorPath
						forResource:"InspectorTemplate" ofType:"nib"];

				// Copy the inspector's template nib file to the correct
				// location.
				[(FileRetriever *)nibFileRetriever setDirectory:inspectorPath];
				[(FileRetriever *)nibFileRetriever setFilename:"data.classes"];
				[nibFileRetriever open];
				
				[(FileSaver *)nibFileSaver setDirectory:inspectorNibDirectory];
				[(FileSaver *)nibFileSaver setFilename:"data.classes"];
				[nibFileSaver save];
	
				[(FileRetriever *)nibFileRetriever setDirectory:inspectorPath];
				[(FileRetriever *)nibFileRetriever setFilename:"data.nib"];
				[nibFileRetriever open];
				
				[(FileSaver *)nibFileSaver setDirectory:inspectorNibDirectory];
				[(FileSaver *)nibFileSaver setFilename:"data.nib"];
				[nibFileSaver save];
				
				[nibSaver freeStream];
			}

			// Make a copy of ToolsAndInspectorProtocols.h.
			[self convertFile:"ToolAndInspectorProtocols" ext:"h"
					destination:"ToolAndInspectorProtocols.h"];
		}
	}
	else
		NXRunAlertPanel("Error", "Missing Class Name", NULL, NULL, NULL);
    return self;
}

// Replace the string XXXX in the template file by the name of the class.
- insertClassname
{
	const char *classname;

	classname = [classnameTextField stringValue];

	while([scratchText findText:"XXXX" ignoreCase:NO backwards:YES wrap:YES])
		[scratchText replaceSel:classname];

	return self;
}

// Replace the string "SETMETHODSM" in ToolTemplate.m by the setMethods
// for the connectors.
- insertSetMethodsInM
{
	[scratchText findText:"SETMETHODSM" ignoreCase:NO backwards:YES wrap:YES];
	if(numConnectors)
		[scratchText replaceSel:setMethodsMString];
	else
		[scratchText replaceSel:""];
	return self;
}

// Replace the string "SETMETHODSH" in ToolTemplate.h by the setMethods
// for the connectors.
- insertSetMethodsInH
{
	[scratchText findText:"SETMETHODSH" ignoreCase:NO backwards:YES wrap:YES];
	if(numConnectors)
		[scratchText replaceSel:setMethodsHString];
	else
		[scratchText replaceSel:""];
	return self;
}

// Replace the string "CONNECTORSTRING" in ToolTemplate.m by the list of
// connectors separated by commas.
- insertConnectorStringInM
{
	[scratchText findText:"CONNECTORSTRING" ignoreCase:NO backwards:YES
			wrap:YES];
	[scratchText replaceSel:connectorString];
	return self;
}

// Replace the string "CONNECTORIDS" in ToolTemplate.h by the id's for 
// the connectors.
- insertConnectorIdStringInH
{
	[scratchText findText:"CONNECTORIDS" ignoreCase:NO backwards:YES
			wrap:YES];
	[scratchText replaceSel:connectorIdString];
	return self;
}

// Get the class template file, create a corresponding class file and
// save it.
- (BOOL)convertFile:(char *)resource ext:(char *)extension
		destination:(char *)classname
{
	NXBundle *mainBundle;
	BOOL success = NO, resourceFound;
	char *lastSlash;

	mainBundle = [NXBundle mainBundle];

	resourceFound = [mainBundle getPath:fullPath forResource:resource
			ofType:extension];
	if(resourceFound)
	{
		lastSlash = rindex(fullPath, '/');
		*lastSlash = '\0';
		strcpy(directory, fullPath);
		strcpy(filename, lastSlash+1);

		[(FileRetriever *)fileRetriever setDirectory:directory];
		[fileRetriever setFilename:filename];

		if([fileRetriever open])
		{
			[self insertClassname];

			if(strcmp("ToolTemplate", resource) == 0)
			{
				if(strcmp(extension, "m") == 0)
				{
					[self insertSetMethodsInM];
					[self insertConnectorStringInM];
				}
				else
				{
					[self insertConnectorIdStringInH];
					[self insertSetMethodsInH];
				}
			}
			
			[fileSaver setFilename:classname];
			if([fileSaver save])
				success = YES;
			else 
				success = NO;
		}
		else
			success = NO;
	}
	else
		success = NO;
		
	return success;
}

// Construct the setMethod strings for Tool.m and Tool.h.  Also construct
// a comma separated list of connectors for Tool.m.
- constructSetMethodStrings
{
	const char *title;
	char *firstTab, *setMethodString, *connectorLine, *lastTab;
	char **connectorNames;
	int rows, cols, row, titleLength, totalNameLength;
	ButtonCell *buttonCell;

	// Construct a list of the connectors the tool will need.
	[connectorMatrix getNumRows:&rows numCols:&cols];
	connectorNames = (char **)malloc(rows * sizeof(char *));
	numConnectors = 0;
	totalNameLength = 0;
	for(row = 0; row < rows; row++)
	{
		buttonCell = [connectorMatrix cellAt:row :0];
		if([buttonCell state])
		{
			title = [buttonCell title];
			titleLength = strlen(title);
			*(connectorNames+numConnectors) = (char *)malloc(titleLength+1);
			strcpy(*(connectorNames+numConnectors), title);
			numConnectors++;
			totalNameLength = totalNameLength + titleLength;
		}
	}	

	connectorIdString = (char *)malloc(totalNameLength + 6 * numConnectors +
			1);
	strcpy(connectorIdString, "");
	for(row = 0; row < numConnectors; row++)
	{
		connectorLine = (char *)malloc(6 + strlen(*(connectorNames+row)) +1);
		sprintf(connectorLine, "\tid\t%s;\n", *(connectorNames+row));
		lastTab = rindex(connectorLine, '\t');
		*(lastTab+1) = tolower(*(lastTab+1));
		strcat(connectorIdString, connectorLine);
		free(connectorLine);
	}

	connectorString = (char *)malloc(totalNameLength+ 4 * numConnectors + 1);
	strcpy(connectorString, "");
	for(row = 0; row < numConnectors; row++)
	{
		strcat(connectorString, "\"");
		strcat(connectorString,	*(connectorNames+row));
		strcat(connectorString, "\"");
		strcat(connectorString, ", ");
	}

	setMethodsMString = (char *)malloc(53 * numConnectors + 2 * 
			totalNameLength + 1);
	strcpy(setMethodsMString,"\n");
	for(row = 0; row < numConnectors; row++)
	{
		setMethodString = (char *) malloc(2 * strlen(*(connectorNames+row))+ 		
				53);	
		sprintf(setMethodString, "- set%s:anObject\n{\n\t%s = "
				"anObject;\n\treturn self;\n}\n\n", *(connectorNames+row),
				*(connectorNames+row));
		
		firstTab = index(setMethodString, '\t');
		*(firstTab+1) = tolower(*(firstTab+1));

		strcat(setMethodsMString, setMethodString);	
		free(setMethodString);
	}
	
	*rindex(setMethodsMString, '\n') = '\0';
		
	setMethodsHString = (char *)malloc(17 * numConnectors +  
			totalNameLength + 2);
	strcpy(setMethodsHString,"");
	for(row = 0; row < numConnectors; row++)
	{
		setMethodString = (char *) malloc(strlen(*(connectorNames+row)) + 17);	
		sprintf(setMethodString, "- set%s:anObject;\n", *(connectorNames+row));
		strcat(setMethodsHString, setMethodString);	
		free(setMethodString);
	}

	return self;
}

- (BOOL)readDataFromStream:(NXStream*)stream
{
	[scratchText readText:stream];
	return YES;
}

- (BOOL)writeDataToStream:(NXStream*)stream
{
	[scratchText writeText:stream];
	return YES;
}

@end
