//========================================================================
//	MShellBuilder.cpp
//	Copyright 1996 Metrowerks Corporation, All Rights Reserved.
//========================================================================	
//	BDS

#include <string.h>
#include <ctype.h>

#include "MShellBuilder.h"
#include "PlugInPreferences.h"
#include "MProject.h"
#include "ErrorMessage.h"

const char *	shellToolName = "sh";

// ---------------------------------------------------------------------------
//	GetToolName
// ---------------------------------------------------------------------------
//	return the name of the tool for which this builder object generates
//	command lines.

long
MShellBuilder::GetToolName(MProject* /* inProject */,
						   char* outName,
						   long	 /*inBufferLength*/,
						   MakeStageT /*inStage*/,
						   MakeActionT /*inAction*/)
{
	strcpy(outName, shellToolName);

	return B_NO_ERROR;
}

// ---------------------------------------------------------------------------
//	LinkerName
// ---------------------------------------------------------------------------
//	return the name of the linker that will link the target files
//	generated by this tool.  This tool's pluginview will only
//	be shown if it matches the tool for the specified project.
//	An empty string matches all linkers.
//	The shell tool isn't associated with a particular linker so
//	it returns an empty string.

const char *
MShellBuilder::LinkerName()
{
	return B_EMPTY_STRING;
}


// ---------------------------------------------------------------------------
//	MakeStages
// ---------------------------------------------------------------------------

MakeStageT
MShellBuilder::MakeStages()
{
	return (kPrecompileStage | kCompileStage | kPostLinkStage);
}

// ---------------------------------------------------------------------------
//	Actions
// ---------------------------------------------------------------------------

MakeActionT
MShellBuilder::Actions()
{
	return (kPrecompile | kCompile | kPostLinkExecute);
}

// ---------------------------------------------------------------------------
//	Flags
// ---------------------------------------------------------------------------

PlugInFlagsT
MShellBuilder::Flags()
{
	return kNotIDEAware;
}

// ---------------------------------------------------------------------------
//	MessageDataType
// ---------------------------------------------------------------------------

ulong
MShellBuilder::MessageDataType()
{
	return kShellMessageType;
}

// ---------------------------------------------------------------------------
//	BuildPrecompileArgv
// ---------------------------------------------------------------------------

long
MShellBuilder::BuildPrecompileArgv(MProject& inProject,
								   BList& inArgv,
								   MFileRec& inFileRec)
{
	return BuildArgv(inProject, inArgv, inFileRec.path);
}

// ---------------------------------------------------------------------------
//	BuildCompileArgv
// ---------------------------------------------------------------------------

long
MShellBuilder::BuildCompileArgv(MProject& inProject,
								BList& inArgv,
								MakeActionT /*inAction*/,
								MFileRec& inFileRec)
{
	return BuildArgv(inProject, inArgv, inFileRec.path);
}

// ---------------------------------------------------------------------------
//	BuildPostLinkArgv
// ---------------------------------------------------------------------------

long
MShellBuilder::BuildPostLinkArgv(MProject& inProject,
								 BList& inArgv,
								 MFileRec& inFileRec)
{
	return BuildArgv(inProject, inArgv, inFileRec.path);
}

// ---------------------------------------------------------------------------
//	BuildArgv
// ---------------------------------------------------------------------------
//	We build the same argv for all stages of make.
//	Don't specify -c or -s as one of the options as these prevent use
//	of a file for input of shell commands.

long
MShellBuilder::BuildArgv(MProject& inProject,
						 BList& inArgv,
						 const char* inFilePath)
{
	long error = B_ERROR;
	char* arg;
	long i = 0;
	long beginOffset = 0;
	
	// While it would be more effecient to save off the preferences in a per/project cache
	// It is easier to get go get them each time...
		
	ShellPrefs shellPrefs = { kCurrentVersion, "-noprofile" };
	ShellPrefs* prefs;
	long length;
	BMessage msg;
	inProject.GetPrefs(kShellMessageType, msg);

	if (msg.FindData(kShellMessageName, kShellMessageType, (const void**)&prefs, &length) == B_NO_ERROR) {
		memcpy(&shellPrefs, prefs, length);	// Make a copy of the prefs
		shellPrefs.SwapBigToHost();
	}
	char* options = shellPrefs.options;
	length = strlen(options);

	// Now iterate the options string
	 
	while (i < length)
	{
		if (isspace(options[i]))
		{
			options[i] = 0;
			arg = strdup(&options[beginOffset]);		// Each argument is allocated by malloc
			inArgv.AddItem(arg);
			
			while (isspace(options[++i]) && i < length)
				{}
			beginOffset = i;
		}
		else
			i++;
	}
	
	if (beginOffset != length)
	{
		arg = strdup(&options[beginOffset]);
		inArgv.AddItem(arg);		
	}
	
	arg = strdup(inFilePath);
	inArgv.AddItem(arg);
	error = B_NO_ERROR;
	
	return error;
}

// ---------------------------------------------------------------------------
//	ValidateSettings
// ---------------------------------------------------------------------------
//	Validate the settings in the message.  If they are not valid for this
//	version of the plug-in or they don't exist then a valid prefs struct is
//	added to the message.

bool
MShellBuilder::ValidateSettings(
	BMessage&	inOutMessage)
{
	ShellPrefs		defaultPrefs = { kCurrentVersion, "-noprofile" };
	bool			changed = false;
	long			len;
	ShellPrefs*		prefsPtr;

	if (B_NO_ERROR == inOutMessage.FindData(kShellMessageName, kShellMessageType, 
											(const void**)&prefsPtr, &len))
	{
		if (B_BENDIAN_TO_HOST_INT32(prefsPtr->version) != kCurrentVersion || len != sizeof(ShellPrefs))
		{
			defaultPrefs.SwapHostToBig();
			inOutMessage.ReplaceData(kShellMessageName, kShellMessageType, &defaultPrefs, sizeof(defaultPrefs));
			changed = true;
		}
	}
	else
	{
		defaultPrefs.SwapHostToBig();
		inOutMessage.AddData(kShellMessageName, kShellMessageType, &defaultPrefs, sizeof(defaultPrefs));
		changed = true;
	}
	
	return changed;
}

// ---------------------------------------------------------------------------
//	FindEndOfLine
// ---------------------------------------------------------------------------

long
MShellBuilder::FindEndOfLine(
	const char*	inText,
	long		inOffset)
{
	while (inText[inOffset] != '\n' && inText[inOffset] != 0)
		inOffset++;

	return inOffset;
}

// ---------------------------------------------------------------------------
//	ParseMessageText
// ---------------------------------------------------------------------------
//	Read the message text and break it into ErrorMessages.  Add each
//	ErrorMessage to the BList.  The text is redirected output of stderr and
//	stdout.  The text is guaranteed to be null terminated.
//	if a line of text begins with the token ';;' it is considered to be a warning
//	all other text becomes an error.
//	this allows use of echo ";;some info text" to be used in the shell script.

long
MShellBuilder::ParseMessageText(MProject& /* inProject */,
								const char* inText,
								BList& outList)
{
	long		beginOffset = 0;
	long		endOfLineOffset = 0;
	
	while (inText[endOfLineOffset] != 0)
	{
		endOfLineOffset = FindEndOfLine(inText, beginOffset);
		long				textLen = endOfLineOffset - beginOffset;

		if (textLen > MAX_ERROR_TEXT_LENGTH)
			textLen = MAX_ERROR_TEXT_LENGTH;

		if (textLen > 1)
		{
			ErrorMessage*	msg = new ErrorMessage;
			
			msg->textonly = true;		// ignore most of the fields in the struct
			msg->isWarning = false;		// it's an error not a warning	
			msg->filename[0] = 0;		// no filename
			
			// check for the special token to indicate a warning
			if (0 == strncmp(&inText[beginOffset], ";;", 2))
			{
				beginOffset += 2;
				textLen -= 2;
				msg->isWarning = true;		// it's  a warning	
			}

			memcpy(msg->errorMessage, &inText[beginOffset], textLen);
			msg->errorMessage[textLen] = '\0';
			
			outList.AddItem(msg);
		}

		beginOffset = endOfLineOffset + 1;
	}
	
	return B_NO_ERROR;
}

// ---------------------------------------------------------------------------
//	CodeDataSize
// ---------------------------------------------------------------------------

void
MShellBuilder::CodeDataSize(MProject& /* inProject */,
							const char* /*inFilePath*/,
							long& outCodeSize,
							long& outDataSize)
{
	outCodeSize = -1;
	outDataSize = -1;
}

// ---------------------------------------------------------------------------
//	GenerateDependencies
// ---------------------------------------------------------------------------

long
MShellBuilder::GenerateDependencies(MProject& /* inProject */,
									const char* /*inFilePath*/,
									BList& /*outList*/)
{
	return B_ERROR;
}

// ---------------------------------------------------------------------------
//	FileIsDirty
// ---------------------------------------------------------------------------
//	return true if the file is dirty and needs to be executed as part of a 
//	make or bring up to date.
//	We always execute shell scripts so always return true.

bool
MShellBuilder::FileIsDirty(MProject& /* inProject */,
						   MFileRec& /*inFileRec*/,
						   MakeStageT /*inStage*/,
						   MakeActionT /*inAction*/,
						   time_t /*inModDate*/)
{
	return true;
}

// ---------------------------------------------------------------------------
//	GetTargetFilePaths
// ---------------------------------------------------------------------------
//	Fill the list with the full paths to the target file(s) generated
//	when the source file is compiled.  This function is called when
//	the linker needs to know which files to link, and when the user
//	chooses the remove objects menu item.
//	The path must be allocated with malloc.  If no target files
//	are generated then simply return.
/*
	// example
	char		filePath[256];
	BFile		file(inFileRef);

	if (B_NO_ERROR == file.GetPath(filePath, 256))
	{
		inOutTargetFileList.AddItem(strdup(filePath));
	}
*/

void
MShellBuilder::GetTargetFilePaths(MProject& /* inProject */,
								 MFileRec& /*inFileRec*/,
								 BList& /*inOutTargetFileList*/)
{
}

// ---------------------------------------------------------------------------
//	ProjectChanged
// ---------------------------------------------------------------------------
//	The MProject callback object allows the builder object to find
//	out various kinds of information about the current project.
//	The MProject object passed in can be cached by the builder object
//	and used at a later time.  It will be valid between calls that 
//	specify kProjectOpened and the matching kProjectClosed.
//	The shell tool doesn't use this info so it doesn't do anything
//	for this callback.

void
MShellBuilder::ProjectChanged(MProject& inProject,
							  ChangeT inChange)
{
	switch (inChange)
	{
		case kProjectOpened:
		case kProjectClosed:
		case kPrefsChanged:
		case kBuildStarted:
		case kFilesAdded:
		case kFilesRemoved:
		case kFilesRearranged:
		case kRunMenuItemChanged:
		case kLinkDone:
			// Don't do anything for now
			break;
	}
}

