// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2004  The 'ac++' developers (see aspectc.org)
//                                                                
// 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.                   
//                                                                
// You should have received a copy of the GNU General Public      
// License along with this program; if not, write to the Free     
// Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA                                           


#include "version.h"
#include "AGxxConfig.h"
#include "file.h"
#include "regex.h"

// PUMA includes
#include "Puma/StrCol.h"
#include "Puma/OptsParser.h"
#include "Puma/VerboseMgr.h"
using namespace Puma;

//stdc++ includes
#include <iostream>
#include <string>


string AGxxConfig::_gcc_option_arg[] = {
	"-aux-info","-print-prog-name=",
	"-idirafter", "-include", "-imacros", "-iprefix",
	"-iwithprefix", "-iwithprefixbefore", "-isystem",
	"-B", "-l", "-I", "-L", "-specs=",
	"-MF", "-MT", "-MQ",
	"-x",
	"--param",
	"-Xlinker", "-u",
	"-V", "-b",
	"-G",
	""/* terminate with empty string */
};

// options for AC++ Compiler
static OptsParser::Option options[] = {
	{ AGxxConfig::AGOPT_GEN_CFG, 0, "gen_config",
		"\t" "Only generate Puma configuration file", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_WEAVE, 0, "weave_only",
		"\t" "Weave only", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_COMPILE_ONLY, 'c', NULL,
		"\t\t" "Compile only", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_KEEP_ACC,0, "keep_acc",
		"\t" "Keep weaved source code files", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_CC, 0, "c_compiler",
		"Path to C++ compiler", OptsParser::AT_MANDATORY },
	{ AGxxConfig::AGOPT_ACC, 0, "ac_compiler",
		"Path to AspectC++ compiler", OptsParser::AT_MANDATORY },
  { AGxxConfig::AGOPT_OUTPUT, 'o', "output",
   "\t" "Name of the output file", OptsParser::AT_MANDATORY },
	{ AGxxConfig::AGOPT_CONFIG_COMMAND,0, "config_command",
	        "Command which prints information about compiler", OptsParser::AT_MANDATORY },
	{ AGxxConfig::AGOPT_X_COMPILER,0, "Xcompiler",
	        "\tIn case of doupt account following options as g++ options", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_X_WEAVER,0, "Xweaver",
	        "\t\tIn case of doupt account following options as ac++ options", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_HELP, 'h', "help",
		"\t\t" "Print this help meassage", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_VERSION, 0, "version",
		"\t" "\t" "Version information", OptsParser::AT_NONE },
	{ AGxxConfig::AGOPT_VERBOSE, 'v', "verbose",
		"Level of verbosity (0-9)", OptsParser::AT_OPTIONAL },
	{ AGxxConfig::ACOPT_INCLUDE_FILES, 'i', "include_files",
		"Generate manipulated header files", OptsParser::AT_NONE },
	{ AGxxConfig::ACOPT_ASPECT_HEADER, 'a', "aspect_header",
		"Name of aspect header file or '0'", OptsParser::AT_MANDATORY }, 
	{ AGxxConfig::ACOPT, 'r', "repository",
		"Name of the project repository", OptsParser::AT_MANDATORY }, 
	{ AGxxConfig::ACOPT, 0, "problem_local_class",
		"Back-end does not support local classes correctly", OptsParser::AT_NONE }, 
	{ AGxxConfig::ACOPT, 0, "no_problem_local_class",
		"Back-end supports local classes correctly", OptsParser::AT_NONE },
	{ AGxxConfig::ACOPT, 0, "problem_spec_scope",
		"Back-end does not support template specialization in non-namespace",OptsParser::AT_NONE }, 
	{ AGxxConfig::ACOPT, 0, "no_problem_spec_scope",
		"Back-end supports template specialization in non-namespace scope",OptsParser::AT_NONE }, 
  { AGxxConfig::ACOPT, 0, "warn_deprecated",
    "\t" "Warn if deprecated syntax is used", OptsParser::AT_NONE },
  { AGxxConfig::ACOPT, 0, "no_warn_deprecated",
    "Don't warn if deprecated syntax is used", OptsParser::AT_NONE },
  { AGxxConfig::ACOPT, 0, "warn_macro",
    "\t" "Warn if a macro transformation had to be ignored", OptsParser::AT_NONE },
  { AGxxConfig::ACOPT, 0, "no_warn_macro",
    "\t" "Don't warn if a macro transformation had to be ignored", OptsParser::AT_NONE },
	{ AGxxConfig::ACOPT, 0,"no_line",
 		"\t\t" "Don't generate #line directives",OptsParser::AT_NONE },
 	{ AGxxConfig::ACOPT, 0,"real-instances",
 		"\t" "Perform full template analysis",OptsParser::AT_NONE },
	{ AGxxConfig::ACOPT_PROJ_PATH, 'p', "path", 
		"\t" "Path to project source", OptsParser::AT_MANDATORY},
	{ AGxxConfig::ACOPT_PROJ_DESTINATION, 'd', "dest", 
		"\t" "Path to destination for modified include files", OptsParser::AT_MANDATORY},
	{ AGxxConfig::ACOPT_CFG_FILE, 0, "config", 
		"\t" "Full path to a config file", OptsParser::AT_MANDATORY}, 
	{ AGxxConfig::ACOPT_PRE_INCLUDE, 'I', NULL, 
		"\t\t" "Add new include path", OptsParser::AT_MANDATORY},
	{ AGxxConfig::ACOPT_PRE_DEFINE, 'D', NULL, 
		"\t\t" "Define a preprocessor macro", OptsParser::AT_MANDATORY},
	{ AGxxConfig::ACOPT_PRE_UNDEFINE, 'U', NULL, 
		"\t\t" "Undefine a preprocessor macro", OptsParser::AT_MANDATORY},
	{ AGxxConfig::ACOPT_ALWAYS_INCLUDE, 0, "include", 
		"\t" "Always include file", OptsParser::AT_MANDATORY},
  { AGxxConfig::ACOPT, 'k', "keywords",
   "\t" "Allow AspectC++ keywords in normal project files", OptsParser::AT_NONE},
	{ 0, 0, 0, 0, OptsParser::AT_NONE}
};

void AGxxConfig::usage() const
 {
   _err << "usage:  ag++ [options] [file...]" << endMessage
        << "   options are ..." << endMessage;
   for (int i = 0; options[i].number > 0; i++) {
     _err << "     ";
     if (options[i].key)  _err << "-" << options[i].key;
     if (options[i].key && options[i].name) _err << "|";
     if (options[i].name) _err << "--" << options[i].name;
     if (options[i].args) _err << " <arg>";
     if (options[i].desc) _err << " \t" << options[i].desc;
     _err << endMessage;
   }
 }

bool AGxxConfig::analyzeGccOpt(OptsParser &op){
	
	// option which should be analyzed
	string opt=op.getOptionName()+op.getArgument();
	
	// argument for this option
	string arg("");
	
	//check for -M/-MM option for generating dependency information
	if (opt.compare("-M") == 0 || opt.compare("-MM") == 0 ){
		_gen_pumaconfig=false;
		_weave=false;
		_link=false;
		_make_dep=true;
		_optvec.pushback(opt,OptionItem::OPT_GCC);
		return true;
	}

	// check if option gets an argument
	for(string* test_str=_gcc_option_arg;test_str->empty()==false;++test_str){
		if( opt.compare(0,test_str->length(),*test_str) == 0){
			
			// ok, it gets an argument
			// check if the argument is appended to the option name
			if ( opt.length() > test_str->length() ){
				// extract the argument out of the option name
				arg="\""+opt.substr(test_str->length())+"\"";
				opt=opt.substr(0,test_str->length());
			}else{
				// check if there exist another option
				if(op.getOption () != OptsParser::FINISH) {
					// check if the next option is an argument (aka NOOPTION)
					if(op.getResult() == OptsParser::NOOPTION){
						arg="\""+op.getArgument()+"\"";
					}else{
						// the next option is NOT an argument 
						_err << sev_error << "G++ Option reqires argument: " ;
						_err <<"'"<<opt.c_str() <<"'"<< endMessage;
						return false;
					}
				}else{
				 	// there is no option anymore
					_err << sev_error << "G++ Option reqires argument: " ;
					_err <<"'"<< opt.c_str() <<"'"<< endMessage;
					return false;
				}
			}
		}
	}
	_optvec.pushback(opt,arg,OptionItem::OPT_GCC);
	return true;

}
					


bool AGxxConfig::analyze(){

	
	// setup default values
	_gen_pumaconfig=true;
	_weave=false;
	_compile=true;
	_link=true;
	_make_dep=false;
	_cc_bin="g++";
	_verbose =0;
	_nr_files =0;
	_keep_acc=false;
	_gen_includes=false;
	_x_compiler=false;
	

		
	OptsParser op(_argc,_argv,options);

	// extract options/filenames out of command line arguments
	while (op.getOption () != OptsParser::FINISH) {
		// first parse options which do not interfere with g++
		// options

		switch (op.getResult ()) {
			
			case AGOPT_OUTPUT :
				_output_file=op.getArgument();
				// we can not push '-o <filename>'
				// into the options vector as afterwards the generation of the
				// puma.config file fails
				continue;
			
			case AGOPT_COMPILE_ONLY :
				_weave=true;
				_link=false;
				_optvec.pushback("-c",OptionItem::OPT_GCC);
				continue;
			
			case AGOPT_X_COMPILER :
				_x_compiler=true;
				continue;
			
			case AGOPT_X_WEAVER :
				_x_compiler=false;
				continue;
			
			case ACOPT_CFG_FILE :
				_puma_config=op.getArgument();
				continue;
				
			case ACOPT_PRE_DEFINE :
				_optvec.pushback("-D","\""+op.getArgument()+"\"",OptionItem::OPT_ACC|OptionItem::OPT_GCC);
				continue;

			case ACOPT_PRE_UNDEFINE :
				_optvec.pushback("-U",op.getArgument(),OptionItem::OPT_ACC|OptionItem::OPT_GCC);
				continue;
			
			case ACOPT_PRE_INCLUDE :
				_optvec.pushback("-I","\""+op.getArgument()+"\"",OptionItem::OPT_ACC|OptionItem::OPT_GCC);
				continue;
			
				
			// command line arguments which are not accepted as options
			// are recognized as filenames
			case OptsParser::NOOPTION :
				++_nr_files;
				// if file is a c++ file weave it first.
				if (regex::regExMatch("^.*\\.((C)|(c)|(cc)|(cp)|(cpp)|(cxx)|(c\\+\\+))$",op.getArgument().c_str()) ){
					if (!_make_dep){
						_weave=true;
					}
					_optvec.pushback(op.getArgument(),OptionItem::OPT_FILE_ACC);
					
					// when calling we have to pass the original path of this 
					// source dir as include path, as the '.acc' file to be 
					// compiled will probably not be generated in the same
					// directory
					string tmpStr=op.getArgument();
					file::stripFilename(tmpStr);
					if (tmpStr.empty()){
						tmpStr =".";
					}
					_optvec.pushback("-I","\""+tmpStr+"\"",OptionItem::OPT_GCC);
				}else{
					_optvec.pushback(op.getArgument(),OptionItem::OPT_FILE_GCC);
				}
				continue;

		}
		
		// if _x_compiler is set to 'true' interprete
		// options that are not handled within this
		// 'switch-case' statement as gcc options.
		if (_x_compiler == true){
			if (analyzeGccOpt(op)==false){
				return false;
			}
			continue;
		}

		
		// check if option is manually assigned to the compiler
			
		switch (op.getResult ()) {
		
			case AGOPT_HELP :
				usage();
				return false;
			
			case AGOPT_VERSION:
				cout << "ag++ " << _VERSION <<"  built: "<<__DATE__<< endl;
				return false;
			
			
			case AGOPT_GEN_CFG :
				_compile=false;
				_link=false;
				break;
			
			case AGOPT_WEAVE :
				_weave=true;
				_compile=false;
				_link=false;
				break;
			
				
			case AGOPT_ACC :
				_acc_bin=op.getArgument();
				break;
			
			case AGOPT_CC :
				_cc_bin=op.getArgument();
				break;
			
			case AGOPT_KEEP_ACC :
				_keep_acc=true;
				break;
				
			case AGOPT_CONFIG_COMMAND :
				_config_command=op.getArgument();
				break;
		
			
			case ACOPT: // default handling for Aspect C++ options
				_optvec.pushback(op.getOptionName(),op.getArgument(),OptionItem::OPT_ACC);
				break;
				
			case ACOPT_INCLUDE_FILES :
				_gen_includes=true;
				_weave=true;
				_compile=false;
				_link=false;
				break;
			
			case AGOPT_VERBOSE :
				_optvec.pushback("-v",op.getArgument(),OptionItem::OPT_ACC);
				if (op.getArgument().empty ()){
					// -v without number
					_verbose = 1;
				}else{
					_verbose = atoi (op.getArgument().c_str());
				}
				if(_verbose > 6){
					_optvec.pushback("-v",OptionItem::OPT_GCC);
				}
				break;
			
			case ACOPT_ASPECT_HEADER :
				_ah_files.push_back(op.getArgument());
				_optvec.pushback("-a","\""+op.getArgument()+"\"",OptionItem::OPT_ACC);
				break;
									  
			case ACOPT_PROJ_PATH :
				_proj_path.push_back(op.getArgument());
				_optvec.pushback("-p","\""+op.getArgument()+"\"",OptionItem::OPT_ACC);
				break;
			
			case ACOPT_PROJ_DESTINATION :
				_dest_path=op.getArgument();
				// not pushed into the '_optvec' vector because:
				// - destination path is only used in WTP mode
				// - WTP mode is only used if --include_files is 
				//   specified
				break;
			
			case ACOPT_ALWAYS_INCLUDE :
				_dest_path=op.getArgument();
				_optvec.pushback("--include","\""+op.getArgument()+"\"",OptionItem::OPT_ACC);
				_optvec.pushback("-include","\""+op.getArgument()+"\"",OptionItem::OPT_GCC);
				break;
			

			case OptsParser::NOARG :
				_err << sev_error << "Option reqires argument: " ;
				_err <<"'"<<op.getOptionName().c_str() <<"'"<< endMessage;
				return false;
			
			case OptsParser::ERR :
				_err << sev_error << " Parsing  Options : ";
				_err << "unspecified error at " ;
				_err <<"'"<<op.getOptionName().c_str() << " " << op.getArgument().c_str() <<"'"<< endMessage;
				return false;
			
			// unknown options are recognized as g++ options
			case OptsParser::UNKNOWN :
				if (analyzeGccOpt(op)==false){
					return false;
				}
				break;
			
			default:
				_err << sev_error << " Parsing  Options : ";
				_err << "option not handled by Ag++ " ;
				_err <<"'"<<op.getOptionName().c_str() << " " << op.getArgument().c_str() <<"'"<< endMessage;
				return false;
		}
	}
	
	
	
	// if we are compiling for WIN32 target transform paths to dos 
	// format, bacause the shell(cmd.exe) can not handle unix 
	// paths
	#if defined (WIN32)
	file::MakeDosPath(_acc_bin);	
	file::MakeDosPath(_cc_bin);
	#endif

	// if path to ac++ was not specified in options.
	// get it from ARGV[0]
	if(_acc_bin.empty()){
		_acc_bin=_argv[0];
		file::stripFilename(_acc_bin);	
		_acc_bin.append("ac++");
	}

	// if there was no project path specified
	// take local directory ('.')
	if(_proj_path.empty()){
		_proj_path.push_back(".");
		_optvec.pushback("-p",".",OptionItem::OPT_ACC);
	}

	
	//
	// Check Constraints
	//
	
	if (_gen_includes==true && _dest_path.empty()){
		_err << sev_error << "Need to specify a destination path in order "
		     << "to generate modified include files" << endMessage;
		return false;
	}

	// If weaving should be done check if there are any files in argument string
	if(_nr_files == 0 && _weave && ! _gen_includes){
		_err << sev_error << "No files found in arguments" << endMessage;
		usage();
		return false;
	}
	
	// If '--weave-only' and '-o' options are used with multiple files 
	// exit with an error
	if( (! (_output_file.empty() || _compile) ) && _nr_files > 1 ){
		_err << sev_error << "Cannot specify -o with --weave_only and multiple compilations"<<endMessage;
		return false;
	}
	
	// If dependencies should be generated don't weave, compile, or generate a config file
	if( (_weave || _link || _gen_pumaconfig) && _make_dep ){
		_err << sev_error << "Invalid selection of options: can't (weave OR link OR generate a config file) AND create dependency information"<<endMessage;
		usage();
		return false;
	}
return true;
}

void AGxxConfig::printOptions(){
	VerboseMgr vm(cout,_verbose);
	vm << endvm;
	vm << endvm;
	vm << "AG++ Configuration:" << endvm;
	vm << "  Create puma.config: " << _puma_config.empty() <<endvm;
	vm << "  Aspect C++ weaver:  " << _acc_bin << endvm;  
	vm << "  C++ compiler:       " << _cc_bin << endvm;  
	vm << "  Files:              " << _optvec.getString(OptionItem::OPT_FILE) << endvm;
	vm << "  Options (G++):      " << _optvec.getString(OptionItem::OPT_GCC) << endvm;
	vm << "  Options (AC++):     " << _optvec.getString(OptionItem::OPT_ACC) << endvm;
	vm << "  Options (total):    " << _optvec.getString() << endvm;
	vm << "  Weave: "<<_weave<<" Compile: "<< _compile<<" Link: "<<_link << endvm;
	vm << endvm;
	vm << endvm;
}

