/*
 * generate-makefile
 *
 * COPYRIGHT: Hutchison Avenue Software, 1994 [514/499-2067, info@hasc.ca]
 *
 * USAGE:
 *
 * cc -o generate-makefile generate-makefile.m -all_load -lMiscKit -lNeXT_s
 *
 * generate-makefile Class.m OtherClass.m OtherOtherClass.m ...
 *
 * CREDITS:
 *
 *    Author: Darcy Brockbank <samurai@hasc.ca>
 *    Date: Sunday, Dec 4, 1994
 *    Version: 1.2
 */
 
static const char * version = "generate-makefile (v1.2)";

/*
 * ADDITIONAL CREDITS:
 *
 * This program makes use of the most excellent MiscKit. If you don't
 * have the MiscKit, you can't compile this program. Luckily for you,
 * the MiscKit is available for free... contact Don Yactman at
 * Don_Yacktman@byu.edu for information on the MiscKit.
 *
 * Modification History:
 *
 * Version 1.0, Dec 4, 1994. samurai@hasc.ca.
 * Version 1.1, Dec 8, 1994. samurai@hasc.ca.
 *	- added ICONSECTIONS to generated makefile
 *	- changed spacing
 *	- added PSWFILES
 * Version 1.2, Dec 15, 1994. samurai@hasc.ca.
 *	- checks to see if a class is in there before adding it.
 *
 * Description:
 *
 * Adds the given Classes to the PB.project in the current directory, and
 * generates a new makefile. This program is pretty stripped down in
 * functionality. I can forsee someone adding a few switches to the program,
 * adding functionality, and this program becoming a nice addition for people
 * using emacs and other configurable editors...
 *
 * Calling this program with no arguments simply transforms the PB.project
 * file in the current directory into a Makefile. So, if you have to make
 * custom changes to the PB.project, you can do so then call this
 * program.
 *
 * Please feel free to add functionality to this, and keep me up to date on
 * new versions! This is only a quick and dirty hack...
 *
 * DISCLAIMER
 *
 * This software is provided as-is, with no warrantee, or guarantee of fitness
 * for any particular purpose. You use this software at your own risk, and the
 * Author(s) may not be held liable for damages resulting from use of this
 * software.
 *
 * COPYING INFORMATION AND GRANTED RIGHTS
 *
 * You may freely copy and modify this software as long as you do not modify
 * any part of this notice, with the following two exceptions:
 *
 * (1) If you modify this code, you must add your name and email address
 *     to the modifiction history, together with an explanation of your
 *     modification.     
 * (2) If your modification changes any functionality already present, you
 *     may modify the description to reflect your changes.
 *     
 */

#import <misckit/MiscString.h>
#import <misckit/MiscParseTableFile.h>
#import <misckit/MiscDictionary.h>
#import <misckit/MiscList.h>
#import <errno.h>

id read_project(const char *filename)
{
	id string = [[MiscString alloc] initString:filename];
	id dictionary = MiscParseTableFile(string);
	if (!dictionary) {
		fprintf(stderr,"Problem opening \"%s\", (%s)\n",
			filename,strerror(errno));
		exit(1);
	}
	[string free];
	return dictionary;
}

id list_from_array(int count, const char * const *names)
{
	int i;
	id list = [[List alloc] init];
	for (i=0;i<count;i++){
		id string = [[MiscString alloc] initString:names[i]];
		[list addObject:string];
	}
	return list;
}

void add_if_absent(id list, id object)
{
	int i, count = [list count];
	for(i=0;i<count;i++){
		if ([[list objectAt:i] isEqual:object]){
			return;
		}
	}
	[list addObject:object];
}

void append_uniquely(id list, id files)
{
	int i, count = [files count];
	for(i=0;i<count;i++){
		add_if_absent(list,[files objectAt:i]);
	}
}

void add_to_mfiles(id mfiles, id dictionary)
{
	id name = [[MiscString alloc] initString:"FILESTABLE"];
	id section = [dictionary valueForKey:name];
	[name setStringValue:"M_FILES"];
	section = [section valueForKey:name];
	append_uniquely(section,mfiles);
	[mfiles empty];
}

void add_to_classes(id mfiles, id dictionary)
{
	id name = [[MiscString alloc] initString:"FILESTABLE"];
	id section = [dictionary valueForKey:name];
	[name setStringValue:"CLASSES"];
	section = [section valueForKey:name];
	append_uniquely(section,mfiles);
	[mfiles empty];
}

void backup(const char *name)
{
	struct stat sb;
	id backup = [[MiscString alloc] initString:name];
	[backup addToEndOfString:"~"];
	if (stat(name,&sb)==0){
		/* we have something to back up */
		if (stat([backup stringValue],&sb)){
			/* error finding old backup */
			if (errno != ENOENT) {
				/* backup was there, but something happened */
				fprintf(stderr,"Error stat'ing old backup"
					" \"%s\", (%s)\n",
					[backup stringValue],strerror(errno));
				exit(1);
			}
		} else {
			/* backup was there, so remove it */
			if (unlink([backup stringValue])){
				fprintf(stderr,"Error unlinking old backup"
					" \"%s\", (%s)\n",
					[backup stringValue],strerror(errno));
				exit(1);
			}
		}
		/* make the backup */
		if (link(name,[backup stringValue])){
			fprintf(stderr,"Error creating backup \"%s\", (%s)\n",
					[backup stringValue],strerror(errno));
			exit(1);
		} else if (unlink(name)){ /* remove the existing file */
			fprintf(stderr,"Error deleting old file \"%s\", (%s)\n",
					name,strerror(errno));
			exit(1);
		}
		
	}
}

void write_project(const char *name, id dictionary)
{
	id string = [[MiscString alloc] initString:name];
	backup(name);
	MiscWriteTableFile(string,dictionary);
	[string free];
}

void write_makefile(const char *name, id dictionary);

/*
 * main
 */
void main(int argc, char *argv[])
{
	id dictionary = read_project("PB.project");
	if (argc>1){
		id mfiles = list_from_array(argc-1,argv+1);
		add_to_classes(mfiles,dictionary);
		write_project("PB.project",dictionary);
	}
	write_makefile("Makefile",dictionary);
}

/*
 * This next chunk handles 80 character wide formatting stuff for the rules.
 * You call format_setup() then call format_next() every time you want to
 * write a string, and it takes care of making sure the correct number of
 * spaces is being added, and that you don't go over the top.
 */
#define PREFIX "    "
#define PREFIX_LEN (strlen(PREFIX))
#define MAX_LEN 80

static int _formatLen;

void format_setup(FILE *file, int startlen)
{
	_formatLen = startlen;
}
void format_next(FILE *file, id string)
{
	_formatLen += ([string strlen]) +1 ;
	if (_formatLen>=MAX_LEN-2){
		fprintf(file,"\\\n%s",PREFIX);
		_formatLen = PREFIX_LEN + ([string strlen]) +1;
	}
	fprintf(file,"%s ",[string stringValue]);
}

/*
 * All the semi-complicated stuff follows here (it's all for generating the
 * makefile. I'd normally not put it after main(), but everything before
 * main() is very simple... 
 */

void write_key(FILE *file, const char *rule, const char *tag, id dict)
{
	id find = [[MiscString alloc] initString:tag];
	id val = [dict valueForKey:find];
	if (val) fprintf(file,"%s = %s\n",rule,[val stringValue]);
	[find free];
}

void write_blank(FILE *file)
{
	fprintf(file,"\n");
}

void write_comment_header(FILE *file)
{
	fprintf(file,
		"#\n"
		"# Generated by \"%s\"\n"
		"#\n"
		"# Copyright (C) 1994, Hutchison Avenue Software.\n"
		"#\n"
		"# \"%s\" is distributable and modifiable, see the source\n"
		"# for limitations and restrictions.\n"
		"#\n"
		"# Contact Darcy Brockbank <samurai@hasc.ca> if you have any problems.\n"
		"#\n",version,version);
}

void write_list_formatted(FILE *file, id list)
{
	int i,count = [list count];
	for(i=0;i<count;i++){
		id string = [list objectAt:i];
		format_next(file,string);
	}
	fprintf(file,"\n");
}

void write_dictionary_formatted(FILE *file, id dict)
{
	id key,val;
	NXHashState state = [dict initState];
	while([dict nextState:&state key:(void **)&key value:(void **)&val]){
		format_next(file,key);
	}
	fprintf(file,"\n");
}

void write_dictionary(FILE *file, const char *rule, const char *tag, id dict)
{
	id find = [[MiscString alloc] initString:tag];
	id subdict = [dict valueForKey:find];
	if (subdict) {
		fprintf(file,"%s = ",rule);
		format_setup(file,strlen(rule)+3);
		write_dictionary_formatted(file,subdict);
	}
	[find free];
}

void write_list(FILE *file, const char *rule, const char *tag, id dict)
{
	id find = [[MiscString alloc] initString:tag];
	id list = [dict valueForKey:find];
	if (list) {
		fprintf(file,"%s = ",rule);
		format_setup(file,strlen(rule)+3);
		write_list_formatted(file,list);
	}
	[find free];
}

void write_global_resources(FILE *file, const char *rule, id dict)
{
	id find = [[MiscString alloc] initString:"FILESTABLE"];
	id filestable = [dict valueForKey:find];
	id local, list;
	const char * resources[] = {"IMAGES", "OTHER_RESOURCES",
					"INTERFACES", 0};
	int i,j,count;

	fprintf(file,"%s = ",rule);
	format_setup(file,strlen(rule)+3);
	[find setStringValue:"LOCALIZABLE_FILES"];
	local = [dict valueForKey:find];
	for(i=0;resources[i];i++){
		[find setStringValue:resources[i]];
		list = [filestable valueForKey:find];
		for(j=0,count=[list count];j<count;j++){
			id current = [list objectAt:j];
			if (![local isKey:current]){
				format_next(file,current);
			}
		}
	}
	fprintf(file,"\n");
	[find free];
}

void write_libs(FILE *file, const char *rule, const char *tag, id dict)
{
	id find = [[MiscString alloc] initString:tag];
	id libs = [dict valueForKey:find];
	int i,count;
	fprintf(file,"%s = ",rule);
	format_setup(file,strlen(rule)+3);
	for(i=0,count=[libs count];i<count;i++){
		id current = [libs objectAt:i];
		[current addToFrontOfString:"-l"];
		format_next(file,current);
	}
	fprintf(file,"\n");
	[find free];
}

void write_iconsections(FILE *file, const char *rule, id dict)
{
	id find = [[MiscString alloc] initString:"DOCICONFILES"];
	id docicons = [dict valueForKey:find];
	id appicon;
	int i,count;
	
	fprintf(file,"%s = ",rule);
	format_setup(file,strlen(rule)+3);
	[find setStringValue:"APPICON"];
	appicon = [dict valueForKey:find];
	if (!appicon) {
		appicon = [[MiscString alloc]
			initString:"/usr/lib/NextStep/Workspace.app/application.tiff"];
	}
	[appicon addToFrontOfString:"-sectcreate __ICON app "];
	format_next(file,appicon);
	for(i=0,count=[docicons count];i<count;i++){
		id current = [docicons objectAt:i];
		id extension = [current fileBasename];
		[current addToFrontOfString:" "];
		[current addToFrontOfString:[extension stringValue]];
		[current addToFrontOfString:"-sectcreate __ICON "];
		format_next(file,current);
		[extension free];
	}
	fprintf(file,"\n");
	[find free];
}

void write_make_info(FILE *file, id dict)
{
	const char * types[] = {"Application", "Bundle", "Palette", "Tool",0};
	const char * files[] = {"app.make", "bundle.make", "palette.make",
				"tool.make",0};
	int i;
	id find = [[MiscString alloc] initString:"PROJECTTYPE"];
	id string;
	
	/* driverkit should do this differently */
	fprintf(file,
		"%s = %s\n",
		"MAKEFILEDIR",
		"/NextDeveloper/Makefiles/app");
	string = [dict valueForKey:find];
	for(i=0;types[i];i++){
		if ([string strcmp:types[i]]==0){
			break;
		}
	}
	if (types[i]){
		fprintf(file,"%s = %s\n","MAKEFILE",files[i]);
	} else {
		fprintf(stderr,"Error... can't find a makefile type\n");
		fprintf(stderr,"You should restore the Makefile from the backup.\n");
		exit(1);
	}
	[find free];
}

void write_string(FILE *file, const char *string)
{
	fprintf(file,"%s\n",string);
}

void write_rule(FILE *file, const char *rule, const char *val)
{
	fprintf(file,"%s = %s\n",rule,val);
}
	
void write_makefile(const char *name, id dict)
{
	FILE * file;
	id find = [[MiscString alloc] initString:"FILESTABLE"];
	id filestable = [dict valueForKey:find];

	[find free];
	backup(name);
	file = fopen(name,"w");
	if (!file) {
		fprintf(stderr,"Can't open \"%s\" for writing, (%s)\n",
			name,strerror(errno));
		exit(1);
	}
	write_comment_header(file);
	write_blank(file);
	write_key(file,"NAME","PROJECTNAME",dict);
	write_blank(file);
	write_key(file,"PROJECTVERSION","PROJECTVERSION",dict);
	write_key(file,"LANGUAGE","LANGUAGE",dict);
	write_key(file,"APPICON","APPICON",dict);
	write_blank(file);
	write_list(file,"DOCICONS","DOCICONFILES",dict);
	write_blank(file);
	write_dictionary(file,"LOCAL_RESOURCES","LOCALIZABLE_FILES",dict);
	write_blank(file);
	write_global_resources(file,"GLOBAL_RESOURCES",dict);
	write_blank(file);
	write_list(file,"CLASSES","CLASSES",filestable);
	write_blank(file);
	write_list(file,"HFILES","H_FILES",filestable);
	write_blank(file);
	write_list(file,"MFILES","M_FILES",filestable);
	write_blank(file);
	write_list(file,"CFILES","C_FILES",filestable);
	write_blank(file);
	write_list(file,"SUBPROJECTS","SUBPROJECTS",filestable);
	write_blank(file);
	write_list(file,"BUNDLES","BUNDLES",filestable);
	write_blank(file);
	write_list(file,"PSWFILES","PSW_FILES",filestable);
	write_blank(file);
	write_list(file,"OTHERSRCS","OTHER_SOURCES",filestable);
	write_blank(file);
	write_blank(file);
	write_make_info(file,dict);
	write_key(file,"INSTALLDIR","INSTALLDIR",dict);
	write_rule(file,"SOURCEMODE","444");
	write_blank(file);
	write_iconsections(file,"ICONSECTIONS",dict);
	write_blank(file);
	write_libs(file,"LIBS","OTHER_LIBS",filestable);
	write_rule(file,"DEBUG_LIBS","$(LIBS)");
	write_rule(file,"PROF_LIBS","$(LIBS)");
	write_blank(file);
	write_string(file,"-include Makefile.preamble");
	write_string(file,"include $(MAKEFILEDIR)/$(MAKEFILE)");
	write_string(file,"-include Makefile.postamble");
	write_string(file,"-include Makefile.dependencies");
}

