//	BLCMDdefine.cc
/*
This source file is part of G4beamline, http://g4beamline.muonsinc.com
Copyright (C) 2003,2004,2005,2006 by Tom Roberts, all rights reserved.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

http://www.gnu.org/copyleft/gpl.html
*/

#include <stdio.h>

#include "BLCommand.hh"
#include "BLParam.hh"

/**	class BLCMDdefine implements the "define" command to define a macro
 *	(an argument-expanded set of commands).
 **/
class BLCMDdefine : public BLCommand {
public:
	BLCMDdefine();

	G4String commandName() { return "define"; }
	
	int command(BLArgumentVector& argv, BLArgumentMap& namedArgs);
};

BLCMDdefine defaultDefine;	// registers the command

/**	class Macro implements a macro command defined by the define command.
 **/
class Macro : public BLCommand {
	G4String name;
	G4String body;
	static int nExpansions;
public:
	Macro(G4String _name, G4String _body) : name(_name), body(_body) 
		{ deleteCommand(name);
		  registerCommand(BLCMDTYPE_OTHER);
		  setSynopsis("Defined macro.");
		}

	G4String commandName() { return name; }
	
	int command(BLArgumentVector& argv, BLArgumentMap& namedArgs);

	void defineNamedArgs() { }
};
int Macro::nExpansions = 0;


BLCMDdefine::BLCMDdefine() : BLCommand()
{
	registerCommand(BLCMDTYPE_CONTROL);
	setSynopsis("defines a macro (argument-expanded set of commands).");
	setDescription("The first argument is the macro name, additional arguments\n"
	  "become lines in the body of the expanded macro. The macro name\n"
	  "becomes a command with up to 9 positional arguments. When the\n"
	  "command is issued, the body is expanded and executed, with\n"
	  "these substitutions:\n"
 	  "  $0     MacroName\n"
	  "  $1-$9  Positional arguments of the command\n"
	  "  $#     # macro expansions (for generating unique names)\n"
	  "NOTE: $paramname is expanded in the define command, but $$paramname "
	  "is expanded when the macro is invoked.");
}

int BLCMDdefine::command(BLArgumentVector& argv, BLArgumentMap& namedArgs)
{
	if(argv.size() < 1) {
		printError("define: need command name being defined");
		return -1;
	}

	// the arguments are the body
	G4String body;
	for(unsigned i=1; i<argv.size(); ++i) {
		if(argv[i].size() > 0) {body += argv[i]; body += "\n";}
	}

	new Macro(argv[0],body);

	// print the definition
	G4String indent1(commandName());
	do { indent1 += " "; } while(indent1.size() < IndentDesc.size());
	printf("%s%s:\n",indent1.c_str(),argv[0].c_str());
	for(unsigned i=1; i<argv.size(); ++i) {
		if(argv[i].size() > 0) 
			printf("%s%s\n",IndentDesc.c_str(),argv[i].c_str());
	}

	return 0;
}

int Macro::command(BLArgumentVector& argv, BLArgumentMap& namedArgs)
{
	++nExpansions;

	if(namedArgs.size() > 0) {
		printError("macro: named arguments are ignored");
	}

	// print the macro command, with args
	G4String line(commandName());
	do { line += " "; } while(line.size() < IndentArg.size());
	for(unsigned i=0; i<argv.size(); ++i)
		{line += argv[i]; line += " ";}
	printf("%s\n",line.c_str());

	// ensure argv has 10 entries. Changes argv in caller, but nobody cares.
	while(argv.size() < 10)
		argv.push_back("");

	// replace: $0 with name, $1 - $9 with argv[0-8], $# with nExpansions
	G4String tmp = Param.expand(body);
	int place=0;
	for(;;) {
		G4String::size_type i = tmp.find('$',place);
		if(i == tmp.npos) break;
		if(tmp.c_str()[i+1] == '#') {
			char tmp2[16];
			sprintf(tmp2,"%d",nExpansions);
			tmp.replace(i,2,tmp2);
		} else if(isdigit(tmp.c_str()[i+1])) {
			int j = tmp.c_str()[i+1] - '0';
			if(j == 0)
				tmp.replace(i,2,name);
			else
				tmp.replace(i,2,argv[j-1]);
		} else {
			place = i+1;
		}
	}

	// execute the macro body (now in tmp)
	G4String::size_type i = 0;
	while(i < tmp.size()) {
		G4String::size_type j = tmp.find('\n',i);
		if(j == tmp.npos) break;
		G4String tmp3(tmp.substr(i,j-i));
		BLCommand::doCommand(tmp3);
		i = j + 1;
	}

	return 0;
}