/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
	mac_xpt_linker.cpp
	
	CodeWarrior plugin linker, links together multiple .xpt files.
	
	by Patrick C. Beard.
 */

/* standard headers */
#include <stdio.h>
#include <string.h>
#include <setjmp.h>

/* system headers */
#include <Files.h>
#include <Strings.h>
#include <Aliases.h>
#include <Resources.h>

/* compiler headers */
#include "DropInCompilerLinker.h"
#include "CompilerMapping.h"
#include "CWPluginErrors.h"

/* project headers */
#include "mac_xpidl_panel.h"
#include "mac_console.h"
#include "mac_strings.h"
#include "FullPath.h"
#include "MoreFilesExtras.h"

/* use standard CodeWarrior debugger */
#define kDebuggerCreator	'MWDB'

/* prototypes of local functions */
static CWResult	Link(CWPluginContext context);
static CWResult	Disassemble(CWPluginContext context);
static CWResult	GetTargetInfo(CWPluginContext context);

extern "C" {
pascal short xpt_linker(CWPluginContext context);
int xptlink_main(int argc, char* argv[]);
int xptdump_main(int argc, char* argv[]);

FILE * FSp_fopen(ConstFSSpecPtr spec, const char * open_mode);
size_t mac_get_file_length(const char* filename);
}

/* external variables */
extern jmp_buf exit_jump;
extern int exit_status;

/* global variables */
CWPluginContext gPluginContext;

/* local variables */
static CWFileSpec gOutputDirectory;
static CWFileSpec gObjectCodeDirectory;

/*
 *	xpt_linker	-	main entry-point for linker plugin
 *
 */
pascal short xpt_linker(CWPluginContext context)
{
	long request;
	if (CWGetPluginRequest(context, &request) != cwNoErr)
		return cwErrRequestFailed;
	
	gPluginContext = context;
	short result = cwNoErr;
		
	/* dispatch on linker request */
	switch (request) {
	case reqInitLinker:
		/* linker has just been loaded into memory */
		break;
		
	case reqTermLinker:
		/* linker is about to be unloaded from memory */
		break;
		
	case reqLink:
		/* build the final executable */
		result = Link(context);
		break;
		
	case reqDisassemble:
		/* disassemble object code for a given project file */
		result = Disassemble(context);
		break;
	
	case reqTargetInfo:
		/* return info describing target characteristics */
		result = GetTargetInfo(context);
		break;
		
	default:
		result = cwErrRequestFailed;
		break;
	}
	
	result = CWDonePluginRequest(context, result);
	
	/* return result code */
	return result;
}

static char* full_path_to(const FSSpec& file)
{
	short len = 0;
	Handle fullPath = NULL;
	if (FSpGetFullPath(&file, &len, &fullPath) == noErr && fullPath != NULL) {
		char* path = new char[1 + len];
		if (path != NULL) {
			BlockMoveData(*fullPath, path, len);
			path[len] = '\0';
		}
		DisposeHandle(fullPath);
		return path;
	}
	return NULL;
}

/**
 * Provides the full path name to a given directory.
 */
static char* full_path_to(short vRefNum, long dirID)
{
	long parID;
	if (GetParentID(vRefNum, dirID, NULL, &parID) == noErr) {
		FSSpec dirSpec = { vRefNum, parID };
		if (GetDirName(vRefNum, dirID, dirSpec.name) == noErr) {
			return full_path_to(dirSpec);
		}
	}
	return NULL;
}

/**
 * Returns the length of a file, assuming it is always located in the
 * project's object code directory.
 */
size_t mac_get_file_length(const char* filename)
{
	FSSpec fileSpec = { gObjectCodeDirectory.vRefNum, gObjectCodeDirectory.parID };
	c2p_strcpy(fileSpec.name, filename);
	long dataSize, rsrcSize;
	if (FSpGetFileSize(&fileSpec, &dataSize, &rsrcSize) != noErr)
		dataSize = 0;
	return dataSize;
}

/**
 * replaces standard fopen -- opens files for writing in the project's output directory,
 * and files for reading in the object code directory.
 */
FILE* std::fopen(const char* filename, const char *mode)
{
	CWFileSpec& fileDir = (mode[0] == 'r' ? gObjectCodeDirectory : gOutputDirectory);
	FSSpec fileSpec = { fileDir.vRefNum, fileDir.parID };
	c2p_strcpy(fileSpec.name, filename);
	return FSp_fopen(&fileSpec, mode);
}

static CWResult GetSettings(CWPluginContext context, XPIDLSettings& settings)
{
	CWMemHandle	settingsHand;
	CWResult err = CWGetNamedPreferences(context, kXPIDLPanelName, &settingsHand);
	if (!CWSUCCESS(err))
		return err;
	
	XPIDLSettings* settingsPtr = NULL;
	err = CWLockMemHandle(context, settingsHand, false, (void**)&settingsPtr);
	if (!CWSUCCESS(err))
		return err;
	
	settings = *settingsPtr;
	
	err = CWUnlockMemHandle(context, settingsHand);
	if (!CWSUCCESS(err))
		return err;

	return cwNoErr;
}

static CWResult LinkHeaders(CWPluginContext context, XPIDLSettings& settings)
{
	// find out how many files there are to link.
	long fileCount = 0;
	CWResult err = CWGetProjectFileCount(context, &fileCount);
	if (err != cwNoErr || fileCount == 0)
		return err;

	// get the output directory.
	FSSpec outputDir;
	err = CWGetOutputFileDirectory(context, &outputDir);
	if (!CWSUCCESS(err))
		return err;
	
	// enumerate all of the output header files, and make aliases to them in
	// the output directory.
	for (long index = 0; (err == cwNoErr) && (index < fileCount); index++) {
		// get the name of each output file.
		CWFileSpec outputFile;
		err = CWGetStoredObjectFileSpec(context, index, &outputFile);
		if (err == cwNoErr) {
			FInfo info;
			err = FSpGetFInfo(&outputFile, &info);
			
			FSSpec aliasFile = { outputDir.vRefNum, outputDir.parID };
			BlockMoveData(outputFile.name, aliasFile.name, 1 + outputFile.name[0]);
			
			AliasHandle alias = NULL;
			if (NewAliasMinimal(&outputFile, &alias) == noErr) {
				// recreate the alias file from scratch.
				FSpDelete(&aliasFile);
				FSpCreateResFile(&aliasFile, info.fdCreator, info.fdType, smRoman);
				short refNum = FSpOpenResFile(&aliasFile, fsRdWrPerm);
				if (refNum != -1) {
					UseResFile(refNum);
					AddResource(Handle(alias), rAliasType, 0, aliasFile.name);
					ReleaseResource(Handle(alias));
					UpdateResFile(refNum);
					CloseResFile(refNum);
				}
				// finally, mark the newly created file as an alias file.
				FSpGetFInfo(&aliasFile, &info);
				info.fdFlags |= kIsAlias;
				FSpSetFInfo(&aliasFile, &info);
			}
		}
	}
	
	// create the target file in the output directory.
	BlockMoveData(settings.output, outputDir.name, 1 + settings.output[0]);
	FILE* outputFile = FSp_fopen(&outputDir, "w");
	if (outputFile != NULL) fclose(outputFile);

	return err;
}

static CWResult LinkTypeLib(CWPluginContext context, XPIDLSettings& settings)
{
	// find out how many files there are to link.
	long fileCount = 0;
	CWResult err = CWGetProjectFileCount(context, &fileCount);
	if (err != cwNoErr || fileCount == 0)
		return err;

	// assemble the argument list.
	// { "xpt_link", outputFile, inputFile1, ..., inputFileN, NULL }
	char** argv = new char*[2 + fileCount + 1];
	int argc = 0;
	argv[argc++] = "xpt_link";

	// get the output directory.
	err = CWGetOutputFileDirectory(context, &gOutputDirectory);
	if (!CWSUCCESS(err))
		return err;
	
	// get the object code directory.
	err = CWGetStoredObjectFileSpec(context, 0, &gObjectCodeDirectory);
	if (!CWSUCCESS(err))
		return err;

	// push the output file name.
	if ((argv[argc++] = p2c_strdup(settings.output)) == NULL)
		return cwErrOutOfMemory;

	for (long index = 0; (err == cwNoErr) && (index < fileCount); index++) {
		// get the name of each output file.
		CWFileSpec outputFile;
		err = CWGetStoredObjectFileSpec(context, index, &outputFile);
		if (err == cwNoErr) {
			if ((argv[argc++] = p2c_strdup(outputFile.name)) == NULL) {
				err = cwErrOutOfMemory;
				break;
			}
		}
	}
	
	if (err != cwNoErr)
		return err;
	
	// trap calls to exit, which longjmp back to here.
	if (setjmp(exit_jump) == 0) {
		if (xptlink_main(argc, argv) != 0)
			err = cwErrRequestFailed;
	} else {
		// evidently the good old exit function got called.
		if (exit_status != 0)
			err = cwErrRequestFailed;
	}
	
	return err;
}

static CWResult	Link(CWPluginContext context)
{
	// load the relevant prefs.
	XPIDLSettings settings = { kXPIDLSettingsVersion, kXPIDLModeTypelib, false, false };
	CWResult err = GetSettings(context, settings);
	if (err != cwNoErr)
		return err;

	switch (settings.mode) {
	case kXPIDLModeHeader:
		return LinkHeaders(context, settings);
	case kXPIDLModeTypelib:
		return LinkTypeLib(context, settings);
	default:
		return cwNoErr;
	}
}

static CWResult	Disassemble(CWPluginContext context)
{
	CWResult err = noErr;

	// cache the project's output directory.
	err = CWGetOutputFileDirectory(gPluginContext, &gOutputDirectory);
	if (!CWSUCCESS(err))
		return err;

	long fileNum;
	err = CWGetMainFileNumber(context, &fileNum);
	if (!CWSUCCESS(err))
		return err;

	// get the output file's location from the stored object data.
	err = CWGetStoredObjectFileSpec(context, fileNum, &gObjectCodeDirectory);
	if (!CWSUCCESS(err))
		return err;
	
	char* outputName = p2c_strdup(gObjectCodeDirectory.name);
	if (outputName == NULL)
		return cwErrOutOfMemory;

	XPIDLSettings settings = { kXPIDLSettingsVersion, kXPIDLModeTypelib, false, false };
	GetSettings(context, settings);

	// build an argument list and call xpt_dump.
	int argc = 1;
	char* argv[] = { "xpt_dump", NULL, NULL, NULL };
	if (settings.verbose) argv[argc++] = "-v";
	argv[argc++] = outputName;
	
	// trap calls to exit, which longjmp back to here.
	if (setjmp(exit_jump) == 0) {
		if (xptdump_main(argc, argv) != 0)
			err = cwErrRequestFailed;
	} else {
		// evidently the good old exit function got called.
		if (exit_status != 0)
			err = cwErrRequestFailed;
	}

	delete[] outputName;

	if (err == noErr) {
		// display the disassembly in its own fresh text window.
		CWNewTextDocumentInfo info = {
			NULL,
			mac_console_handle,
			false
		};
		CWResizeMemHandle(context, mac_console_handle, mac_console_count);
		err = CWCreateNewTextDocument(context, &info);
	}

	return err;
}

static CWResult	GetTargetInfo(CWPluginContext context)
{
	CWTargetInfo targ;
	memset(&targ, 0, sizeof(targ));
	
	CWResult err = CWGetOutputFileDirectory(context, &targ.outfile);
	targ.outputType = linkOutputFile;
	targ.symfile = targ.outfile;	/* location of SYM file */
	targ.linkType = exelinkageFlat;
	targ.targetCPU = '****';
	targ.targetOS = '****';
	
	// load the relevant settings.
	XPIDLSettings settings = { kXPIDLSettingsVersion, kXPIDLModeTypelib, false, false };
	err = GetSettings(context, settings);
	if (err != cwNoErr)
		return err;
	
#if CWPLUGIN_HOST == CWPLUGIN_HOST_MACOS
	// tell the IDE about the output file.
	targ.outfileCreator		= 'MMCH';
	targ.outfileType		= 'CWIE';
	targ.debuggerCreator	= kDebuggerCreator;	/* so IDE can locate our debugger	*/

	BlockMoveData(settings.output, targ.outfile.name, 1 + settings.output[0]);
	targ.symfile.name[0] = 0;
#endif

#if CWPLUGIN_HOST == CWPLUGIN_HOST_WIN32
	targ.debugHelperIsRegKey = true;
	*(long*)targ.debugHelperName = kDebuggerCreator;
	targ.debugHelperName[4] = 0;
	strcat(targ.outfile.path, "\\");
	strcat(targ.outfile.path, prefsData.outfile);
	strcpy(targ.symfile.path, targ.outfile.path);
	strcat(targ.symfile.path, ".SYM");
#endif

	targ.runfile			= targ.outfile;
	targ.linkAgainstFile	= targ.outfile;

	/* we can only run applications */
	// targ.canRun = (prefsData.projtype == kProjTypeApplication);
	
	/* we can only debug if we have a SYM file */
	// targ.canDebug = prefsData.linksym;	
	
	err = CWSetTargetInfo(context, &targ);
	
	return err;
}

#if 0

#if CW_USE_PRAGMA_EXPORT
#pragma export on
#endif

CWPLUGIN_ENTRY(CWPlugin_GetDropInFlags)(const DropInFlags** flags, long* flagsSize)
{
	static const DropInFlags sFlags = {
		kCurrentDropInFlagsVersion,
		CWDROPINLINKERTYPE,
		DROPINCOMPILERLINKERAPIVERSION_7,
		(linkMultiTargAware | linkAlwaysReload),
		0,
		DROPINCOMPILERLINKERAPIVERSION
	};
	
	*flags = &sFlags;
	*flagsSize = sizeof(sFlags);
	
	return cwNoErr;
}

CWPLUGIN_ENTRY(CWPlugin_GetDropInName)(const char** dropinName)
{
	static const char* sDropInName = "xpt Linker";
	*dropinName = sDropInName;
	return cwNoErr;
}

CWPLUGIN_ENTRY(CWPlugin_GetDisplayName)(const char** displayName)
{
	static const char* sDisplayName = "xpt Linker";
	*displayName = sDisplayName;
	return cwNoErr;
}

CWPLUGIN_ENTRY(CWPlugin_GetPanelList)(const CWPanelList** panelList)
{
	// +++Turn this on when the sample panel has been converted!
	static const char* sPanelName = kXPIDLPanelName;
	static CWPanelList sPanelList = { kCurrentCWPanelListVersion, 1, &sPanelName };
	
	*panelList = &sPanelList;
	
	return cwNoErr;
}

CWPLUGIN_ENTRY(CWPlugin_GetTargetList)(const CWTargetList** targetList)
{
	static CWDataType sCPU = '****';
	static CWDataType sOS = '****';
	static CWTargetList sTargetList = { kCurrentCWTargetListVersion, 1, &sCPU, 1, &sOS };
	
	*targetList = &sTargetList;
	
	return cwNoErr;
}

CWPLUGIN_ENTRY(CWPlugin_GetDefaultMappingList)(const CWExtMapList** defaultMappingList)
{
	static CWExtensionMapping sExtension = { 'MMCH', ".xpt", 0 };
	static CWExtMapList sExtensionMapList = { kCurrentCWExtMapListVersion, 1, &sExtension };
	
	*defaultMappingList = &sExtensionMapList;
	
	return cwNoErr;
}

CWPLUGIN_ENTRY (CWPlugin_GetFamilyList)(const CWFamilyList** familyList)
{
	static CWFamily sFamily = { 'XIDL', "xpidl Settings" };
	static CWFamilyList sFamilyList = { kCurrentCWFamilyListVersion, 0, &sFamily };
	
	*familyList = &sFamilyList;
	
	return cwNoErr;
}

#if CW_USE_PRAGMA_EXPORT
#pragma export off
#endif

#endif
