#include <wx/menu.h>
#include <wx/string.h>
#include <wx/ffile.h>
#include "mxMainWindow.h"
#include "ProjectManager.h"
#include "mxCustomTools.h"
#include "mxHelpWindow.h"
#include "ConfigManager.h"
#include "ids.h"
#include "Language.h"
#include "mxMessageDialog.h"
#include "mxSource.h"
#include "Parser.h"
#include "parserData.h"
#include "mxOutputView.h"
#include "mxProjectStatistics.h"
#include "mxCompiler.h"
#include "mxTextDialog.h"
#include "mxStatusBar.h"
#include "mxWxfbInheriter.h"
#include "mxDiffWindow.h"
#include "mxDiffSideBar.h"
#include "mxDoxyDialog.h"
#include "mxOSD.h"
#include "mxShareSourceWindow.h"
#include "mxOpenSharedWindow.h"
#include "mxDrawClasses.h"
#include "mxFlowWindow.h"
#include "mxExeInfo.h"
#include "mxColoursEditor.h"
#include "mxListSharedWindow.h"
#include "mxComplementInstallerWindow.h"
#include "mxCppCheckConfigDialog.h"
#include "mxValgrindConfigDialog.h"
#include "Toolchain.h"
#include "mxNewWizard.h"
#include <fstream>
#include "mxArt.h"
#include "mxMakefileDialog.h"
#include <wx/file.h>
#include <wx/textfile.h>
#include "mxGprofOutput.h"
#include "execution_workaround.h"
#include "mxWxfbConfigDialog.h"
#include "MenusAndToolsConfig.h"
#include "mxLizardOutput.h"
#include "mxStyledOutput.h"
#include <wx/wfstream.h>
#include <wx/txtstrm.h>
#include "mxCompilerArgEnabler.h"
using namespace std;

/// @brief Muestra el cuadro de configuración de cppcheck (mxCppCheckConfigDialog)
void mxMainWindow::OnToolsCppCheckConfig(wxCommandEvent &event) {
	if (project) new mxCppCheckConfigDialog(this);
}

/// @brief Lanza cppcheck sobre los fuentes del proyecto en una mxOutputWindow
void mxMainWindow::OnToolsCppCheckRun(wxCommandEvent &event) {
	if (!config->CheckCppCheckPresent()) return;
	if (!project && !notebook_sources->GetPageCount()) return;
	mxOutputView *cppcheck = new mxOutputView("CppCheck",mxOV_EXTRA_NULL,"","",mxVO_CPPCHECK,DIR_PLUS_FILE(config->temp_dir,"cppcheck.out"));
	
	wxString file_args, cppargs, toargs, extra_args, path;
	
	if (project) {
		
		// files
		project->SaveAll(false);
		if (!project->cppcheck) project->cppcheck=new cppcheck_configuration;
		wxArrayString files,exclude_list;
		mxUT::Split(project->cppcheck->exclude_list,exclude_list,true,false);
		project->GetFileList(files,FT_SOURCE,true);
		if (!project->cppcheck->exclude_headers) project->GetFileList(files,FT_HEADER,true);
		wxString list(DIR_PLUS_FILE(config->temp_dir,"cppcheck.lst"));
		wxFile flist(list,wxFile::write);
		char el='\n';
		for (unsigned int i=0;i<files.GetCount();i++) {
			if (exclude_list.Index(files[i])==wxNOT_FOUND) {
				flist.Write(/*mxUT::Quotize(*/DIR_PLUS_FILE(project->path,files[i])/*)*/);
				flist.Write((void*)&el,1);
			}
		}
		flist.Close();
		file_args=wxString("--file-list=")<<mxUT::Quotize(list);

		project->AnalizeConfig(project->path,true,current_toolchain.mingw_dir,true);
		toargs=project->cpp_compiling_options;
		
		// extra_args
		if (project->cppcheck->copy_from_config)
			extra_args<<mxUT::Split(project->cppcheck->config_d,"-D")<<" "<<mxUT::Split(project->cppcheck->config_u,"-U");
		
		// cppargs
		cppargs<<mxUT::Split(project->cppcheck->style,"--enable=")<<" ";
		cppargs<<mxUT::Split(project->cppcheck->platform,"--platform=")<<" ";
		cppargs<<mxUT::Split(project->cppcheck->standard,"--std=")<<" ";
		cppargs<<mxUT::Split(project->cppcheck->suppress_ids,"--suppress=")<<" ";
		if (project->cppcheck->suppress_file.Len()) cppargs<<"--suppressions_list="<<mxUT::Quotize(DIR_PLUS_FILE(project->path,project->cppcheck->suppress_file))<<" ";
		if (project->cppcheck->inline_suppr) cppargs<<"--inline-suppr ";
		
		// path
		path=project->path;
		
	} else {
		
		mxSource *src = CURRENT_SOURCE;
		
		//files
		file_args = mxUT::Quotize(src->SaveSourceForSomeTool());
		
		// toargs
		toargs=src->GetCompilerOptions(true);
		
		// cppargs
		cppargs="--enable=all --inline-suppr";
		
		// path
		path=src->working_folder.GetFullPath();
		
	}
	
	// args, lo que sale de las opciones de compilacion
	wxString args; // -D..., -I...., -U....
	wxArrayString array;
	mxUT::Split(toargs,array,false,true);
	for (unsigned int i=0;i<array.GetCount();i++) {
		if ((!project || project->cppcheck->copy_from_config) && array[i].StartsWith("-D")) {
			if (array[i].Len()==2) {
				args<<" -D "<<array[++i];
			} else {
				args<<" -D "<<array[i].Mid(2);
			}
		} else if ((!project || project->cppcheck->copy_from_config) && array[i].StartsWith("\"-D")) {
			if (array[i]=="\"-D\"")
				args<<" -D "<<array[++i];
			else
				args<<" -D \""<<array[i].Mid(3);
		} else if (array[i].StartsWith("-I")) {
			if (array[i].Len()==2) {
				args<<" -I "<<array[++i];
			} else {
				args<<" -I "<<array[i].Mid(2);
			}
		} else if (array[i].StartsWith("\"-I")) {
			if (array[i]=="\"-I\"")
				args<<" -I "<<array[++i];
			else
				args<<" -I \""<<array[i].Mid(3);
		}
	}
	if (extra_args.Len()) args<<" "<<extra_args;
	
	wxString command = mxUT::Quotize(config->Files.cppcheck_command)<<" "<<cppargs<<" --template \"[{file}:{line}] ({severity},{id}) {message}\" "<<args<<" "<<file_args;
	_IF_DEBUGMODE(command);
	cppcheck->Launch(path,command);
	
}

/// @brief Despliega el panel de valgrind para mostrar los resultados de CppCheck
void mxMainWindow::OnToolsCppCheckView(wxCommandEvent &event) {
	ShowValgrindPanel(mxVO_CPPCHECK,DIR_PLUS_FILE(config->temp_dir,"cppcheck.out"));
}

void mxMainWindow::OnToolsCppCheckHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("cppcheck.html");	
}

void mxMainWindow::OnToolsProjectStatistics(wxCommandEvent &evt) {
	if (project) new mxProjectStatistics(this);
}

#ifndef __WIN32__
/**
* @brief Prepara una corrida para ejecutarse con Valgrind
*
* Coloca el comando de valgrind en compiler->valgrind_cmd, que es lo que se va a 
* usar al preparar la ejecucion. Despues llama a Ejecutar del menu Ejecutar, 
* y al limpia limpia compiler->valgrind_cmd para que la proxima ejecucion
* vuelva a ser normal
**/
void mxMainWindow::OnToolsValgrindCommon(bool debug) {
	if (!config->CheckValgrindPresent()) return;
	mxValgrindConfigDialog valgrind_config(this);
	valgrind_config.SetArg("--vgdb-error=0",debug);
	if (valgrind_config.ShowModal()==0) return;
	
	wxString val_file = DIR_PLUS_FILE(config->temp_dir,"valgrind.out");
	val_file.Replace(" ","\\ ");
	compiler->valgrind_cmd = config->Files.valgrind_command+" "<<valgrind_config.GetArgs()<<" --log-file="+val_file;
	wxCommandEvent event;
	OnRunRun(event);
	compiler->valgrind_cmd="";
	if (debug) DebugTargetCommon("remote | vgdb",true);
}

void mxMainWindow::OnToolsValgrindRun(wxCommandEvent &event) {
	OnToolsValgrindCommon(false);
}
void mxMainWindow::OnToolsValgrindDebug(wxCommandEvent &event) {
	OnToolsValgrindCommon(true);
}

/// @brief Despliega el panel de Valgrind para mostrar los resultados del analisis dinamico
void mxMainWindow::OnToolsValgrindView(wxCommandEvent &event) {
	ShowValgrindPanel(mxVO_VALGRIND,DIR_PLUS_FILE(config->temp_dir,"valgrind.out"));
}

void mxMainWindow::OnToolsValgrindHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("valgrind.html");	
}

#endif

void mxMainWindow::OnToolsConsole(wxCommandEvent &evt) {
	wxString path=config->zinjai_dir;
	if (project) 
		path=project->path;
	else IF_THERE_IS_SOURCE 
		path=CURRENT_SOURCE->GetPath(true);
#if defined(__APPLE__)
	mxUT::Execute(path,"/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal",wxEXEC_NOHIDE);
#elif defined(__WIN32__)
	mxUT::Execute(path,"cmd",wxEXEC_NOHIDE);
#else
	mxUT::Execute(path,config->Files.terminal_command.BeforeFirst(' '),wxEXEC_NOHIDE);
#endif
}

void mxMainWindow::OnToolsWxfbConfig(wxCommandEvent &event) {
	if (project) new mxWxfbConfigDialog();
}

void mxMainWindow::OnToolsWxfbNewRes(wxCommandEvent &event) {
	if (project) {
		
		if (!project->GetWxfbActivated()) project->ActivateWxfb(true);
		
		wxString name=mxGetTextFromUser("Nombre del archivo:","Nuevo Proyecto wxFormBuilder","",this);
		if (name.Len()==0) return;
		
		wxString fname=DIR_PLUS_FILE(project->path,name);
		wxString fbase=fname;
		if (mxUT::EndsWithNC(fbase,".FBP"))
			fbase=fbase.Mid(0,fbase.Len()-4);
		else
			fname = fname+".fbp";
		
		wxString folder=project->path;
		int pos1=fname.Find('\\',true);
		int pos2=fname.Find('/',true);
		if (pos1!=wxNOT_FOUND && pos2!=wxNOT_FOUND) {
			int pos = pos1>pos2?pos1:pos2;
			folder=fname.Mid(0,pos);
			name=fname.Mid(pos+1);
		} else if (pos1!=wxNOT_FOUND) {
			folder=fname.Mid(0,pos1);
			name=fname.Mid(pos1+1);
		} else if (pos2!=wxNOT_FOUND) {
			folder=fname.Mid(0,pos2);
			name=fname.Mid(pos2+1);
		}
		
		if (wxNOT_FOUND!=name.Find('-') 
			|| wxNOT_FOUND!=name.Find('<') || wxNOT_FOUND!=name.Find('?') 
			|| wxNOT_FOUND!=name.Find('>') || wxNOT_FOUND!=name.Find('*') ) 
		{
			mxMessageDialog(this,"El nombre ingresado no es valido").Title(LANG(GENERAL_ERROR,"Error")).IconError().Run();
			return;
		}
		
		// controlar que no exista
		wxString cpp_name = fbase+".cpp";
		wxString h_name = fbase+".h";
		if (wxFileName::FileExists(fname)) {
			if (mxMessageDialog(this,"Ya existe un archivo con ese nombre. Desea conservarlo?\nSi elige No se borrara su contenido.")
					.Title(name).IconWarning().Run().ok)
			{
				wxRemoveFile(fname);
			}
		} 
		
		// crear el cpp
		if (!wxFileName::FileExists(cpp_name)) {
			wxTextFile cpp_file(cpp_name);
			cpp_file.Create();
			cpp_file.Write();
			cpp_file.Close();
		}
		
		// crear el h
		if (!wxFileName::FileExists(h_name)) {
			wxTextFile h_file(h_name);
			h_file.Create();
			h_file.Write();
			h_file.Close();
		}
		
		if (mxUT::EndsWithNC(name,".FBP")) name=name.Mid(0,name.Len()-4);
		if (!wxFileName::FileExists(fname)) {
			// crear el fbp
			wxTextFile fbp_file(fname);
			fbp_file.Create();
			fbp_file.AddLine("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?>");
			fbp_file.AddLine("<wxFormBuilder_Project>");
			fbp_file.AddLine("<FileVersion major=\"1\" minor=\"10\" />");
			fbp_file.AddLine("<object class=\"Project\" expanded=\"1\">");
			fbp_file.AddLine("<property name=\"class_decoration\"></property>");
			fbp_file.AddLine("<property name=\"code_generation\">C++</property>");
			fbp_file.AddLine("<property name=\"disconnect_events\">1</property>");
			fbp_file.AddLine("<property name=\"encoding\">ANSI</property>");
			fbp_file.AddLine("<property name=\"event_generation\">connect</property>");
			fbp_file.AddLine(wxString("<property name=\"file\">")<<name<<"</property>");
			fbp_file.AddLine("<property name=\"first_id\">1000</property>");
			fbp_file.AddLine("<property name=\"help_provider\">none</property>");
			fbp_file.AddLine("<property name=\"internationalize\">0</property>");
			fbp_file.AddLine(wxString("<property name=\"name\">")<<name<<"</property>");
			fbp_file.AddLine("<property name=\"namespace\"></property>");
			fbp_file.AddLine("<property name=\"path\">.</property>");
			fbp_file.AddLine("<property name=\"precompiled_header\"></property>");
			fbp_file.AddLine("<property name=\"relative_path\">1</property>");
			fbp_file.AddLine("<property name=\"use_enum\">0</property>");
			fbp_file.AddLine("<property name=\"use_microsoft_bom\">0</property>");
			fbp_file.AddLine("</object>");
			fbp_file.AddLine("</wxFormBuilder_Project>");
			fbp_file.Write();
			fbp_file.Close();
		}
		
		// asociar al proyecto y abrir
		
		wxfb_configuration *wxfb = project->GetWxfbConfiguration();
		
		project_file_item *item=project->HasFile(cpp_name);
		if (!item) item=project->AddFile(FT_SOURCE,cpp_name);
		if (wxfb->set_wxfb_sources_as_readonly) project->SetFileReadOnly(item,true);
		if (wxfb->dont_show_base_classes_in_goto) project->SetFileHideSymbols(item,true);
		
		item=project->HasFile(h_name);
		if (!item) item=project->AddFile(FT_HEADER,h_name);
		if (wxfb->set_wxfb_sources_as_readonly) project->SetFileReadOnly(item,true);
		if (wxfb->dont_show_base_classes_in_goto) project->SetFileHideSymbols(item,true);
		
		if (!project->HasFile(fname)) project->AddFile(FT_OTHER,fname);
		
		main_window->OpenFile(fname,false);
		return;
	}
}

void mxMainWindow::OnToolsWxfbLoadRes(wxCommandEvent &event) {
	if (project) {
		
		wxFileDialog dlg (this, "Abrir Archivo", project?project->last_dir:config->Files.last_dir, " ", "Any file (*)|*", wxFD_OPEN | wxFD_FILE_MUST_EXIST );
		dlg.SetWildcard("wxFormBuilder projects|"WILDCARD_WXFB);
		if (dlg.ShowModal() != wxID_OK) return;
		
		project->last_dir=dlg.GetDirectory();
		
		wxString fbp_name = dlg.GetPath();
		
		wxString fbase = project->WxfbGetSourceFile(fbp_name);
		
		wxFileName fn_header(fbase+".h");
		fn_header.Normalize();
		wxString h_name=fn_header.GetFullPath();
		
		wxFileName fn_source(fbase+".cpp");
		fn_source.Normalize();
		wxString cpp_name=fn_source.GetFullPath();
		
		if (!project->GetWxfbActivated()) project->ActivateWxfb(true);
		
		if (!project->HasFile(cpp_name))
			project->AddFile(FT_SOURCE,cpp_name);
		if (!project->HasFile(h_name))
			project->AddFile(FT_HEADER,h_name);
		if (!project->HasFile(fbp_name))
			project->AddFile(FT_OTHER,fbp_name);
//		main_window->OpenFile(fbp_name,false);
		project->WxfbGenerate(true,project->HasFile(fbp_name));
		parser->Parse();
		
	}
}

void mxMainWindow::OnToolsWxfbRegen(wxCommandEvent &event) {
	if (project && project->GetWxfbActivated()) {
		status_bar->SetStatusText("Regenerando proyectos wxFormBuilder...");
		project->WxfbGenerate();
//		parser->Parse();
//		status_bar->SetStatusText(LANG(GENERAL_READY,"Listo"));
	}
}

void mxMainWindow::OnToolsWxfbHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("wxformbuilder.html");
}

void mxMainWindow::OnToolsWxfbHelpWx(wxCommandEvent &event) {
	if (wxFileName::FileExists(DIR_PLUS_FILE(config->zinjai_dir,config->Help.wxhelp_index)))
		mxUT::OpenInBrowser(DIR_PLUS_FILE(config->zinjai_dir,config->Help.wxhelp_index));
	else if (mxMessageDialog(this,"ZinjaI no pudo encontrar la ayuda de wxWidgets. A continuacion le permitira buscarla\n"
								  "manualmente y luego recordara esta seleccion (en cualquier momento se puede modificar\n"
								  "desde el cuadro de Preferencias). Usualmente, el archivo indice es \"wx_contents.html\".")
				.Title("Ayuda wxWidgets").IconInfo().ButtonsOkCancel().Run().ok) 
	{
		wxFileDialog dlg(this,"Indice de ayuda wxWidgets:",config->Help.wxhelp_index);
		if (wxID_OK==dlg.ShowModal()) {
			config->Help.wxhelp_index=dlg.GetPath();
			mxUT::OpenInBrowser(DIR_PLUS_FILE(config->zinjai_dir,config->Help.wxhelp_index));
		}
	}
}


class ToolsWxfbInheriterAction : public Parser::OnEndAction {
	bool update;
public:
	ToolsWxfbInheriterAction(bool _update):update(_update){}
	void Run() override { new mxWxfbInheriter(main_window,"",update); }
};

void mxMainWindow::OnToolsWxfbInheritClass(wxCommandEvent &event) {
	OnToolsWxfbRegen(event);
	parser->OnEnd(new ToolsWxfbInheriterAction(false),true);
}

void mxMainWindow::OnToolsWxfbUpdateInherit(wxCommandEvent &event) {
	OnToolsWxfbRegen(event);
	parser->OnEnd(new ToolsWxfbInheriterAction(true),true);
}

void mxMainWindow::OnToolsRemoveComments (wxCommandEvent &event) {
	IF_THERE_IS_SOURCE CURRENT_SOURCE->RemoveComments();
}

void mxMainWindow::OnToolsAlignComments (wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		mxSource *src=CURRENT_SOURCE;
		long l=-1;
		wxString input=mxGetTextFromUser("Alinear Comentarios","Nro de Columna:",wxString()<<config->Source.alignComments,this);
		if (input.Len() && input.ToLong(&l) && l>=0) 
			src->AlignComments(config->Source.alignComments=l);
	}
}

void mxMainWindow::OnToolsDiffPrevMark(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE
		CURRENT_SOURCE->GotoDiffChange(false);
}

void mxMainWindow::OnToolsDiffNextMark(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE
		CURRENT_SOURCE->GotoDiffChange(true);
}

void mxMainWindow::OnToolsDiffTwoSources(wxCommandEvent &event) {
	if (notebook_sources->GetPageCount()>1)
		new mxDiffWindow(nullptr);
	else
		mxMessageDialog(main_window,"Debe tener al menos dos archivos abiertos para poder compararlos")
			.Title(LANG(GENERAL_ERROR,"Error")).IconWarning().Run();		
}

void mxMainWindow::OnToolsDiffToHimself(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		mxSource *src = CURRENT_SOURCE;
		if (src->sin_titulo) {
			mxMessageDialog(main_window,"El archivo actual no ha sido guardado")
					.Title(LANG(GENERAL_ERROR,"Error")).IconWarning().Run();		
		} else {
			if (src->GetModify())
				new mxDiffWindow(CURRENT_SOURCE,src->source_filename.GetFullPath());
			else
				mxMessageDialog(main_window,"El archivo actual no ha sido modificado")
					.Title(LANG(GENERAL_ERROR,"Error")).IconWarning().Run();		
		}
	} else
		mxMessageDialog(main_window,"Debe tener al menos un archivo abierto para poder compararlo")
			.Title(LANG(GENERAL_ERROR,"Error")).IconWarning().Run();		
}

void mxMainWindow::OnToolsDiffToDiskFile(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		wxFileDialog dlg (this, "Abrir Archivo", project?project->last_dir:config->Files.last_dir, " ", "Any file (*)|*", wxFD_OPEN | wxFD_FILE_MUST_EXIST );
		dlg.SetWildcard("Archivos de C/C++|"WILDCARD_CPP"|Fuentes|"WILDCARD_SOURCE"|Cabeceras|"WILDCARD_HEADER"|Todos los archivos|*");
		if (dlg.ShowModal() == wxID_OK) {
			if (project)
				project->last_dir=dlg.GetDirectory();
			else
				config->Files.last_dir=dlg.GetDirectory();
			new mxDiffWindow(CURRENT_SOURCE,dlg.GetPath());
		}
	} else
		mxMessageDialog(main_window,"Debe tener al menos un archivo abierto para poder compararlo")
			.Title(LANG(GENERAL_ERROR,"Error")).IconWarning().Run();
}

void mxMainWindow::OnToolsDiffClear(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		mxSource *src = CURRENT_SOURCE;
		src->MarkDiffs(0,src->GetLineCount()-1,mxSTC_MARK_DIFF_NONE);
		src->Refresh();
		if (diff_sidebar) diff_sidebar->Refresh();
	}
}

void mxMainWindow::OnToolsDoxyConfig(wxCommandEvent &event) {
	if (project) new mxDoxyDialog(this);
}

void mxMainWindow::OnToolsDoxyGenerate(wxCommandEvent &event) {
	if (project) {
		if (config->CheckDoxygenPresent()) {
			mxOutputView *doxy = new mxOutputView("Doxygen",mxOV_EXTRA_URL,"Ver HTMLs",
				DIR_PLUS_FILE(project->path,DIR_PLUS_FILE(project->GetDoxygenConfiguration()->destdir,DIR_PLUS_FILE("html","index.html"))),
				mxVO_DOXYGEN,DIR_PLUS_FILE(config->temp_dir,"doxygen.out"));
			project->SaveAll(false);
			project->GenerateDoxyfile(DIR_PLUS_FILE(project->path,"Doxyfile"));
			doxy->Launch(project->path,wxString("\"")<<config->Files.doxygen_command<<"\" Doxyfile");
		}
	}
}

void mxMainWindow::OnToolsDoxyView(wxCommandEvent &event) {
	if (project) {
		mxUT::OpenInBrowser(DIR_PLUS_FILE(project->path,DIR_PLUS_FILE(project->GetDoxygenConfiguration()->destdir,DIR_PLUS_FILE("html","index.html"))));
	}
}


void mxMainWindow::OnToolsDoxyHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("doxygen.html");
}

void mxMainWindow::OnToolsDiffHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("diff.html");
}

void mxMainWindow::OnToolsShareHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("share.html");
}

void mxMainWindow::OnToolsGprofHelp (wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("gprof.html");
}

static bool auxOnToolsGprofGcovSet(wxString tool, wxString arg) {
	if (!project && main_window->notebook_sources->GetPageCount()==0) return false;
	mxCompilerArgEnabler dlg(main_window,tool,
							 LANG1(COMP_ARG_GENERIC_INFO,""
								   "Para poder generar la información de profiling que\n"
								   "analiza <{1}>, se deben utilizar argumentos específicos\n"
								   "al compilar, luego recompilar todo el proyecto/programa\n"
								   "para que estos cambios tengan efecto (si no lo hace ahora,\n"
								   "deberá luego utilizar el comando \"Limpiar\" del menú \n"
								   "\"Ejecución\") y finalmente ejecutar el programa.",tool),
							 arg,arg);
	return dlg.ShowModal() && dlg.GetUserSelection();
}

void mxMainWindow::OnToolsGprofSet (wxCommandEvent &event) {
	auxOnToolsGprofGcovSet("GProf","-pg");
}

void mxMainWindow::OnToolsGprofDot (wxCommandEvent &event) {
	_menu_item(mxID_TOOLS_GPROF_DOT)->Check(true);
	_menu_item(mxID_TOOLS_GPROF_FDP)->Check(false);
	config->Init.graphviz_dot=true;
}

void mxMainWindow::OnToolsGprofFdp (wxCommandEvent &event) {
	_menu_item(mxID_TOOLS_GPROF_DOT)->Check(false);
	_menu_item(mxID_TOOLS_GPROF_FDP)->Check(true);
	config->Init.graphviz_dot=false;	
}


static void showExternToolErrorMessage(int retval, wxArrayString &errors, wxString tool) {
	wxString msg = mxUT::UnSplit(errors,"\n");
	if (msg.Len()) msg=wxString("\n\n")+msg;
	mxOSD::HideCurrent();
	mxMessageDialog(main_window,
		LANG1(MAINW_EXTERN_TOOL_ERROR,"Ha ocurrido un error al ejecutar <{1}>.",tool)
		+(wxString(" (error ")<<retval<<").")+msg)
		.Title(LANG(GENERAL_ERROR,"Error")).IconError().Run(); 
}

/**
* Verifica que exita información de profiling para el fuente o proyecto actual,
* y ejecuta gprof para extraerla un archivo de texto. Retorna el path del archivo
* generado. Si hay error retorna una cadena vacia.
**/
wxString mxMainWindow::OnToolsGprofShowListAux(bool include_command) {
	if (!project && notebook_sources->GetPageCount()==0) return "";
	
	// ver si hay informacion de profiling
	wxString gmon = project ? (DIR_PLUS_FILE(project->active_configuration->working_folder.Len()?DIR_PLUS_FILE(project->path,project->active_configuration->working_folder):project->path,"gmon.out")) : (DIR_PLUS_FILE(CURRENT_SOURCE->working_folder.GetFullPath(),"gmon.out"));
	if (!wxFile::Exists(gmon)) {
		mxMessageDialog(this,LANG(MAINW_GPROF_OUTPUT_MISSING,""
								  "No se encontro informacion de profiling.\n"
								  "Para saber como generarla consulte la ayuda."))
			.Title(LANG(GENERAL_ERROR,"Error")).IconError().Run();
		return "";
	}
	
	// ejecutar gprof...
	status_bar->SetStatusText(LANG(MAINW_GPROF_STATUS_ANALIZING,"Analizando informacion de perfilado..."));
	mxOSDGuard osd(this,LANG(OSD_GENERATING_GRAPH,"Generando grafo..."));
	wxString command("gprof -b ");
	if (project)
		command<<mxUT::Quotize(project->GetExePath());
	else IF_THERE_IS_SOURCE
		command<<mxUT::Quotize(CURRENT_SOURCE->GetBinaryFileName().GetFullPath());
	command<<" "<<mxUT::Quotize(gmon);
	// ...y capturar salida en un archivo de texto
	wxString gout = DIR_PLUS_FILE(config->temp_dir,"gprof.txt");
	wxRemoveFile(gout); // eliminar resultados previos para evitar problemas
	wxFFile fgout(gout,"w+");
	wxArrayString output, errors;
	if (include_command) fgout.Write(wxString("> ")<<command+"\n\n");
	int retval=mxExecute(command, output, errors, wxEXEC_NODISABLE|wxEXEC_SYNC);
	if (retval||!output.GetCount()) { // si hay algun error al ejecutar gprof
		showExternToolErrorMessage(retval, errors,"gprof");
		return "";
	}
	for (unsigned int i=0;i<output.GetCount();i++)
		fgout.Write(output[i]+"\n");
	fgout.Close();
	
	return gout;
}

void mxMainWindow::OnToolsGprofShow (wxCommandEvent &event) {
	
	// procesar binario de gprof para obtener la salida como texto
	wxString gout=OnToolsGprofShowListAux(); 
	if (!gout.Len()) return;
	
	mxOSDGuard osd(this,LANG(OSD_GENERATING_GRAPH,"Generando grafo..."));
	
	// pedir al usuario los limites para el grafo (arcos y nodos con poco peso se descartan)
	static double edge_tres=0.1,node_tres=0.1;
	wxString edgt = mxGetTextFromUser(LANG(MAINW_GPROF_EDGE_THERSHOLD,"Eliminar arcos bajo este umbral (%):"),LANG(MAINW_GPROF_ASK_TITLE,"Perfil de Ejecucion"),wxString()<<edge_tres,this);
	if (!edgt.Len()) return;
	edgt.Replace(",",".");
	wxString nodt = mxGetTextFromUser(LANG(MAINW_GPROF_NODE_THERSHOLD,"Eliminar nodos bajo este umbral (%):"),LANG(MAINW_GPROF_ASK_TITLE,"Perfil de Ejecucion"),wxString()<<node_tres,this);
	if (!nodt.Len()) return;
	nodt.Replace(",",".");
	
	// procesar salida con gprof2dot para obtener el grafo
	status_bar->SetStatusText(LANG(MAINW_GPROF_DRAWING,"Dibujando grafo..."));
	wxString pout = DIR_PLUS_FILE(config->temp_dir,"gprof.dot");
	wxString command = mxUT::Quotize(DIR_PLUS_FILE(
		config->zinjai_third_dir,
		OSDEP_VAL("gprof2dot/gprof2dot.exe","gprof2dot/gprof2dot.py") ));
	nodt.ToDouble(&edge_tres); edgt.ToDouble(&node_tres);
	command<<" "<<mxUT::Quotize(gout)<<" -e "<<edge_tres<<" -n "<<node_tres<<" -o "<<mxUT::Quotize(pout);
	int retval=mxExecute(command,wxEXEC_NODISABLE|wxEXEC_SYNC);
	_IF_DEBUGMODE( command + (wxString("\nretval: ")<<retval) );
	if (retval) { 
		osd.Hide(); 
		mxMessageDialog(this,wxString(LANG(MAINW_GPROF_ERROR,"Ha ocurrido un error al intentar procesar la información de perfilado"))+" (error 2).")
			.Title(LANG(GENERAL_ERROR,"Error")).IconError().Run(); 
		return; 
	}
	
	// mostrar el grafo
	status_bar->SetStatusText(LANG(MAINW_GPROF_SHOWING,"Mostrando resultados..."));
	osd.Hide(); // ProcessGraph will show its own one
	mxUT::ProcessGraph(pout,!config->Init.graphviz_dot,"",LANG(MAINW_GPROF_GRAPH_TITLE,"Informacion de Profiling"));
}

void mxMainWindow::OnToolsGprofList (wxCommandEvent &event) {
	
	// procesar binario de gprof para obtener la salida como texto
	wxString gout=OnToolsGprofShowListAux(); 
	if (!gout.Len()) return;
	
	// analizar la salida del archivo de texto y mostrar en nueva ventana
	new mxGprofOutput(this,gout);
//	mxSource *src=OpenFile(gout,false);
//	if (src && src!=EXTERNAL_SOURCE) src->MakeUntitled("<profiling>");
	status_bar->SetStatusText(LANG(GENERAL_READY,"Listo"));
	
}

void mxMainWindow::OnToolsDiffApply(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		CURRENT_SOURCE->ApplyDiffChange();
		if (diff_sidebar) diff_sidebar->Refresh();
	}
}

void mxMainWindow::OnToolsDiffDiscard(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		CURRENT_SOURCE->DiscardDiffChange();
		if (diff_sidebar) diff_sidebar->Refresh();
	}
}

void mxMainWindow::OnToolsDiffShow(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE
		CURRENT_SOURCE->ShowDiffChange();
}

void mxMainWindow::OnToolsShareShare (wxCommandEvent &event) {
	IF_THERE_IS_SOURCE 
		new mxShareSourceWindow(CURRENT_SOURCE,CURRENT_SOURCE->page_text,this);
}

void mxMainWindow::OnToolsShareOpen (wxCommandEvent &event) {
	mxOpenSharedWindow::Show();
}

void mxMainWindow::OnToolsShareList(wxCommandEvent &event) {
	new mxListSharedWindow(this);
}

void mxMainWindow::OnToolsDrawProject(wxCommandEvent &event) {
	if (project) project->DrawGraph();
}

void mxMainWindow::OnToolsDrawClasses(wxCommandEvent &event) {
	if (project) {
		project->UpdateSymbols();
	} else {
		IF_THERE_IS_SOURCE
			parser->ParseSource(CURRENT_SOURCE);
	}
	new mxDrawClasses();
}

/// @brief manda a dibujar un diagrama de flujo (de eso se encargan mxFlowWindow y mxFlowCanvas)
void mxMainWindow::OnToolsDrawFlow(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		mxSource *source = CURRENT_SOURCE;
		new mxFlowWindow(source, source->page_text);
	}
}

void mxMainWindow::OnToolsExeProps (wxCommandEvent &event) {
	if (project) 
		mxExeInfo::RunForProject(this);
	else IF_THERE_IS_SOURCE
		mxExeInfo::RunForSimpleProgram(this,CURRENT_SOURCE);
}

void mxMainWindow::OnToolsCustomToolsSettings(wxCommandEvent &evt) {
	new mxCustomTools(false,-1);
}

void mxMainWindow::OnToolsProjectToolsSettings(wxCommandEvent &evt) {
	new mxCustomTools(true,-1);
}

void mxMainWindow::OnToolsCustomHelp(wxCommandEvent &evt) {
	mxHelpWindow::ShowHelp("custom_tools.html");	
}

void mxMainWindow::OnToolsCustomTool(wxCommandEvent &event) {
	config->custom_tools.Run(event.GetId()-mxID_CUSTOM_TOOL_0);
}

void mxMainWindow::OnToolsCustomProjectTool(wxCommandEvent &event) {
	project->custom_tools.Run(event.GetId()-mxID_CUSTOM_PROJECT_TOOL_0);
}

class ToolsCodeCopyFromHAction : public Parser::OnEndAction {
	mxSource *source;
	wxString the_one;
public:
	ToolsCodeCopyFromHAction(mxSource *_source,wxString _the_one):source(_source),the_one(_the_one){}
	void Run() override { main_window->ToolsCodeCopyFromH(source,the_one); }
};

void mxMainWindow::OnToolsCodeCopyFromH(wxCommandEvent &event) {
	IF_THERE_IS_SOURCE {
		mxSource *source = CURRENT_SOURCE;
		wxString the_one;
		if (source->sin_titulo || !(the_one=mxUT::GetComplementaryFile(source->source_filename,FT_SOURCE)).Len()) {
//			mxMessageDialog(this,LANG(MAINW_CODETOOLS_NO_HEADER_FOUND,"No se pudo determinar el archivo de cabecera asociado."),LANG(GENERAL_WARNING,"Advertencia"),mxMD_WARNING|mxMD_OK).ShowModal();
//			return;
			the_one = source->GetFullPath();
		}
		mxSource *other=IsOpen(the_one);
		parser->ParseSource(source);
		if (other) parser->ParseSource(other);
		else parser->ParseFile(the_one);
		parser->OnEnd(new ToolsCodeCopyFromHAction(source,the_one),true);
	}
}
		
void mxMainWindow::ToolsCodeCopyFromH(mxSource *source, wxString the_one) {
	pd_file *file=parser->GetFile(the_one);
	if (!file) return;
	mxSource::UndoActionGuard undo_action(source);
	pd_ref *func;
	func=file->first_func_dec;
	wxArrayString choices;
		while (func->next) { func=func->next; } // para atras, para que queden ordenadas como en el h
		while (func->prev) {
			if (!PD_UNREF(pd_func,func)->file_def && !(PD_UNREF(pd_func,func)->properties&PD_CONST_VIRTUAL_PURE)) {
				choices.Add(PD_UNREF(pd_func,func)->full_proto+((PD_UNREF(pd_func,func)->properties&PD_CONST_CONST)?" const":"")); // const no deberia estar en el full proto?
			}
			func=func->prev;
		}
	undo_action.End();
	if (!choices.GetCount()) {
		mxMessageDialog(this,LANG(MAINW_CODETOOLS_NO_NEW_METHOD_FUNCTION,"No se encontraron funciones/metodos sin implementar."))
			.Title(LANG(GENERAL_WARNING,"Advertencia")).IconWarning().Run();
		return;
	}
	wxArrayInt sels;
	int line=-1;
	for (unsigned int i=0;i<choices.GetCount();i++) sels.Add(i);
	int nsels=wxGetMultipleChoices(sels,LANG(MAINW_CODETOOLS_CHOOSE_FUNCTIONS_METHODS,"Seleccione las funciones/metodos a implementar:"),LANG(MENUITEM_TOOLS_CODE_COPY_FROM_H,"Implementar Metodos/Funciones faltantes"),choices,this);
	for (int i=0;i<nsels;i++) {
		while (source->GetLineCount() && source->GetLine(source->GetLineCount()-1).Len()>1) {
			wxString aux=source->GetLine(source->GetLineCount()-1);
			source->AppendText("\n");
		}
		while (source->GetLineCount()>1 && source->GetLine(source->GetLineCount()-2).Len()>1) {
			wxString aux=source->GetLine(source->GetLineCount()-2);
			source->AppendText("\n");
		}
		if (line==-1) line=source->GetLineCount();
		source->AppendText(choices[sels[i]]+" {\n\t\n}\n\n");
	}
	if (line>=0) {
		source->EnsureVisibleEnforcePolicy(source->GetLineCount()-1);
		source->MarkError(line,false);
	}
}

static void GetValidLines(const char *fin, int *v, int n) {
	memset(v,0,sizeof(int)*n);
	ifstream f(fin);
	string l; int ln=1;
	getline(f,l);
	string fname=l.substr(l.find('\"'),l.rfind('\"')-l.find('\"')+1);
	bool counting=false; int old_last,last=0;
	while (getline(f,l)) {
		if (l.size()>2 && l[0]=='#' && l[1]==' ') {
			int n=l.size(), i=2;
			while (i<n&&l[i]!=' ') i++;
			old_last=last; last=atoi(l.substr(2,i).c_str());
			while (i<n&&l[i]==' ') i++;
			if (i!=n) {
				if (counting) v[old_last-1]=ln+1;
				counting=(l.substr(i,l.rfind('\"')-i+1)==fname);
			}
		} else if (counting) {
			v[(last++)-1]=l.length()?ln:0;
		}
		ln++;
	}
	f.close();
}

void mxMainWindow::OnToolsPreprocMarkValid ( wxCommandEvent &event ) {
	IF_THERE_IS_SOURCE ToolsPreproc(1);
}

void mxMainWindow::OnToolsPreprocUnMarkAll ( wxCommandEvent &event ) {
	IF_THERE_IS_SOURCE {
		mxSource *src=CURRENT_SOURCE;
		src->IndicatorSetStyle(2,wxSTC_INDIC_STRIKE);
		int lse = src->GetEndStyled();
		src->StartStyling(0,wxSTC_INDICS_MASK);
		src->SetStyling(src->GetLength(),0);
		src->StartStyling(lse,0x1F);
	}
}

void mxMainWindow::OnToolsPreprocReplaceMacros ( wxCommandEvent &event ) {
	IF_THERE_IS_SOURCE ToolsPreproc(2);
}

void mxMainWindow::OnToolsPreprocHelp ( wxCommandEvent &event ) {
	mxHelpWindow::ShowHelp("menu_herramientas.html#expandir_macros");	
}

/**
* @brief Realiza algunas de las acciones del submenú Preprocesador del menú Herramientas
*
* @param id_commnad 1=marcar lineas validas, 2=reemplazar macros
**/
void mxMainWindow::ToolsPreproc( int id_command ) {
	
	mxOSDGuard osd(this,LANG(MAINW_PREPROC_OSD,"Preprocesando..."));
	mxSource *src=CURRENT_SOURCE;
	wxString bin_name;
	bin_name = DIR_PLUS_FILE(config->temp_dir,"preprocessed.tmp");
	if (project) {
		if (src->GetModify()) src->SaveSource();
//		file_item *item = project->FindFromName(src->source_filename.GetFullPath());
		wxString fname = src->source_filename.GetFullPath();
		project->AnalizeConfig(project->path,true,current_toolchain.mingw_dir,true);
		bool cpp = fname.Last()!='c';
		wxString command = wxString(cpp?current_toolchain.cpp_compiler:current_toolchain.c_compiler)+
			(cpp?project->cpp_compiling_options:project->c_compiling_options)+" "+mxUT::Quotize(fname)+" -c -E -o "+mxUT::Quotize(bin_name);
		if (id_command==1) command<<" -fdirectives-only -C";
		_IF_DEBUGMODE(command);
		int x =mxUT::Execute(project->path,command, wxEXEC_SYNC/*|wxEXEC_HIDE*/);	
		if (x!=0) { 
			_IF_DEBUGMODE(x);
			osd.Hide();
			mxMessageDialog(this,LANG(MAINW_PREPROC_ERROR,"No se pudo preprocesar correctamente el fuente."))
				.Title(LANG(GENERAL_ERROR,"Error")).IconError().Run();;
			return;
		}
	} else {
		wxString fname=src->SaveSourceForSomeTool();
		wxString z_opts(wxString(" "));
		// prepare command line
		bool cpp = src->IsCppOrJustC();
		if (config->Debug.format.Len()) z_opts<<config->Debug.format<<" ";
		z_opts<<(cpp?current_toolchain.cpp_compiling_options:current_toolchain.c_compiling_options)<<" ";
//		z_opts<<current_toolchain.linker_options<<" ";
		if (!src->sin_titulo) {
			eFileType ftype = mxUT::GetFileType(src->source_filename.GetFullName(),false);
			if (ftype!=FT_HEADER&&ftype!=FT_SOURCE) z_opts<<(src->IsCppOrJustC()?"-x c++ ":"-x c "); 
		}
		z_opts<<"-E "; if (id_command==1) z_opts<<"-fdirectives-only -C ";
		wxString comp_opts = src->GetCompilerOptions();
		wxString command = wxString(cpp?current_toolchain.cpp_compiler:current_toolchain.c_compiler)+z_opts+"\""+fname+"\" "+comp_opts+" -o \""+bin_name<<"\"";
		_IF_DEBUGMODE(command);
		int x =mxUT::Execute(src->source_filename.GetPath(),command, wxEXEC_SYNC/*|wxEXEC_HIDE*/);	
		if (x!=0) {
			_IF_DEBUGMODE(x);
			osd.Hide();
			mxMessageDialog(this,LANG(MAINW_PREPROC_ERROR,"No se pudo preprocesar correctamente el fuente."))
				.Title(LANG(GENERAL_ERROR,"Error")).IconError().Run();;
			return;
		}
	}
	int n=src->GetLineCount();
	int *v=new int[n];
	GetValidLines(bin_name.c_str(),v,n);
	
	
	if (id_command==1) {
		src->IndicatorSetStyle(2,wxSTC_INDIC_STRIKE);
		if (g_ctheme->DEFAULT_BACK.Green()+g_ctheme->DEFAULT_BACK.Blue()+g_ctheme->DEFAULT_BACK.Red()<256*3/2)
			src->IndicatorSetForeground (2, wxColour(255,255,255));
		else
			src->IndicatorSetForeground (2, wxColour(0,0,0));
		int lse = src->GetEndStyled();
		src->StartStyling(0,wxSTC_INDICS_MASK);
		src->SetStyling(src->GetLength(),0);
		for(int i=0;i<n;i++) { 
			if (i&&src->GetCharAt(src->GetLineEndPosition(i-1)-1)=='\\') v[i]=v[i-1];
			if (!v[i]) {
				int i1=src->GetLineIndentPosition(i);
				int i2=src->GetLineEndPosition(i);
				// descomentar para que no tache el #if... que da verdadero
//				if (
//					(i1+3<i2 && src->GetTextRange(i1,i1+3)=="#if")||
//					(i1+3<i2 && src->GetTextRange(i1,i1+5)=="#else")||
//					(i1+3<i2 && src->GetTextRange(i1,i1+5)=="#elif")
//					) {
//						int j=i+1;
//						while(j<n && src->GetLineIndentPosition(j)==src->GetLineEndPosition(j)) j++;
//						if (j<n && v[j]) v[i]=true;
//					}
//				if (!v[i]) {
					src->StartStyling(i1,wxSTC_INDICS_MASK);
					src->SetStyling(i2-i1,wxSTC_INDIC2_MASK);
//				}
			}
		}
		delete []v;
		src->StartStyling(lse,0x1F);
	} else if (id_command==2) {
		int l0=src->LineFromPosition(src->GetSelectionStart());
		int l1=src->LineFromPosition(src->GetSelectionEnd());
		if (l1<l0) { int x=l1; l1=l0; l0=x; }
		wxTextFile fil(bin_name);  fil.Open();
		wxString ret;
		for(int i=l0;i<=l1;i++) {
			ret<<(v[i]?fil.GetLine(v[i]):"")<<"\n";
		}
		ret.RemoveLast();
		src->ShowBaloon(ret,src->PositionFromLine(l0?l0-1:0));
	}
//	src->SetFocus();
}

void mxMainWindow::OnToolsInstallComplements(wxCommandEvent &evt) {
	new mxComplementInstallerWindow(this);
}

void mxMainWindow::OnToolsCreateTemplate(wxCommandEvent &evt) {
	wxString user_templates_dir=DIR_PLUS_FILE(config->config_dir,"templates");
	if (!wxFileName::DirExists(user_templates_dir)) 
		wxFileName::Mkdir(user_templates_dir);
	if (project) {
		wxString description=wxGetTextFromUser(LANG(MAINW_ENTER_FRIENDLY_NAME_FOR_NEW_PROJECT_TEMPLATE,"Ingrese el nombre para mostrar del nuevo template de proyecto"),LANG(MAINW_GENERATING_TEMPLATE,"Generando plantilla..."),project->project_name);
		if (!description.Len()) return; 
		wxString filename=wxGetTextFromUser(LANG(MAINW_ENTER_FILE_NAME_FOR_NEW_PROJECT_TEMPLATE,"Ingrese el nombre del archivo del nuevo template de proyecto"),LANG(MAINW_GENERATING_TEMPLATE,"Generando plantilla..."),wxFileName(project->filename).GetName());
		if (!filename.Len()) return; 
		mxOSDGuard osd(this,LANG(MAINW_GENERATING_TEMPLATE,"Generando plantilla..."));
		project->Clean(); wxYield(); // remove temporals
		wxString project_name=project->project_name; project->project_name=description; // replace project name with the new description
		project->Save(true); // save in place as template
		project->project_name=project_name; // restore original project name
		project->CleanAll(); // remove temp files
		wxString dest_dir=DIR_PLUS_FILE(user_templates_dir,filename);
		wxFileName::Mkdir(dest_dir); mxUT::XCopy(project->path,dest_dir,true); // copy all project files
		wxString zpr=wxFileName(project->filename).GetFullName(); 
		wxString orig_zpr=DIR_PLUS_FILE(dest_dir,zpr);
		wxString dest_zpr=DIR_PLUS_FILE(dest_dir,filename+DOT_PROJECT_EXT);
		if (dest_zpr!=orig_zpr) wxRenameFile(orig_zpr,dest_zpr); // rename .zpr
		project->Save(); // restore real project file to non-template status
	} else IF_THERE_IS_SOURCE {
		wxString description = wxGetTextFromUser(LANG(MAINW_ENTER_FRIENDLY_NAME_FOR_NEW_SIMPLE_PROGRAM_TEMPLATE,"Ingrese el nombre para mostrar del nuevo template de programa simple"));
		if (!description.Len()) return; 
		wxString filename = wxGetTextFromUser(LANG(MAINW_ENTER_FILE_NAME_FOR_NEW_SIMPLE_PROGRAM_TEMPLATE,"Ingrese el nombre del archivo del nuevo template de programa simple"));
		if (!filename.Len()) return; 
		mxSource *src=CURRENT_SOURCE;
		wxString dest_file=DIR_PLUS_FILE(user_templates_dir,filename+".tpl");
		// create the template file
		wxTextFile template_file(dest_file);
		template_file.Create();
		template_file.AddLine(wxString("// !Z! Name: ")+description);
		int cur_pos=src->GetCurrentPos(),delta=0;
		for(int i=0;i<cur_pos;i++) { if (src->GetCharAt(i)=='\r') delta++; }
		template_file.AddLine(wxString("// !Z! Caret: ")<<(cur_pos-delta));
		template_file.AddLine(wxString("// !Z! Options: ")+src->GetCompilerOptions(false));
		if (!src->cpp_or_just_c) template_file.AddLine(wxString("!Z! Type: C"));
		for(int i=0;i<src->GetLineCount();i++) {
			wxString str=src->GetLine(i);
			while (str.EndsWith("\r")||str.EndsWith("\n")) str.RemoveLast();
			template_file.AddLine(str);
		}
		template_file.Write(); template_file.Close();
	}
	mxMessageDialog(this,LANG(MAINW_TEMPLATE_GENERATED,"Plantilla generada"))
		.Title(LANG(MENUITEM_TOOLS_CREATE_TEMPLATE,"Guardar como nueva plantilla...")).Run();
	delete g_wizard; g_wizard =nullptr;
}

void mxMainWindow::OnToolsExportMakefile (wxCommandEvent &event) {
	new mxMakefileDialog(this);
}


void mxMainWindow::OnToolsGcovSet (wxCommandEvent & event) {
	if (auxOnToolsGprofGcovSet("GCov","--coverage"))
		ShowGCovSideBar();
}

void mxMainWindow::OnToolsGcovReset (wxCommandEvent & event) {
	if (project) {
		wxString path=project->GetTempFolder();
		if ( mxMessageDialog(this,wxString("Se eliminarán todos los archivos con extensión .gcov"/*, .gcno*/" y .gcda del"
										   "\ndirectorio de temporales (")<<path<<")\n¿Desea Continuar?")
				.Title("gcov").ButtonsYesNo().IconWarning().Run().no) return;
		wxArrayString array;
		mxUT::GetFilesFromDir(array,path,true);
		for(unsigned int i=0;i<array.GetCount();i++) { 
			if (array[i].EndsWith(".gcov")||array[i].EndsWith(".gcda")/*||array[i].EndsWith(".gcno")*/)
				wxRemoveFile(DIR_PLUS_FILE(path,array[i]));
		}
	} else IF_THERE_IS_SOURCE {
		mxSource *src=CURRENT_SOURCE;
		wxRemoveFile(DIR_PLUS_FILE(src->GetPath(false),src->GetFileName(true))+".gcov");
		wxRemoveFile(DIR_PLUS_FILE(src->GetPath(false),src->GetFileName(false))+".gcda");
//		wxRemoveFile(DIR_PLUS_FILE(src->GetPath(false),src->GetFileName(false))+".gcno");
	}
}

void mxMainWindow::OnToolsGcovShow (wxCommandEvent & event) {
	ShowGCovSideBar();
}

void mxMainWindow::OnToolsGcovHelp (wxCommandEvent & event) {
	mxHelpWindow::ShowHelp("gcov.html");
}

void mxMainWindow::OnToolsLizardRun(wxCommandEvent &event) {
	if (!project && notebook_sources->GetPageCount()==0) return;
		
	status_bar->SetStatusText(LANG(MAINW_GPROF_STATUS_LIZARD,"Ejecutando lizard..."));
	mxOSDGuard osd(this,LANG(MAINW_GPROF_STATUS_LIZARD,"Ejecutando lizard..."));
	
	// ejecutar lizard...
	wxString command(DIR_PLUS_FILE_2(config->zinjai_third_dir,"lizard",OSDEP_VAL("lizard.exe","lizard.py")));
	if (project) {
		wxArrayString files;
		project->GetFileList(files,FT_SOURCE,false);
		project->GetFileList(files,FT_HEADER,false);
		for (unsigned int i=0;i<files.GetCount();i++)
			command << " " << mxUT::Quotize(files[i]);
	} else {
		command<<mxUT::Quotize(CURRENT_SOURCE->SaveSourceForSomeTool());
	}
	// ...y capturar salida 
	wxArrayString output,errors;
	int retval=mxExecute(command, output, errors, wxEXEC_NODISABLE|wxEXEC_SYNC);
	if (/*retval||*/!output.GetCount()) { // si hay algun error al ejecutar lizard
		showExternToolErrorMessage(retval, errors, "lizard");
		return;
	}
	// si todo fue bien, mostrar la ventana de resultados
	new mxLizardOutput(this,output);
}

void mxMainWindow::OnToolsLizardHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("lizard.html");
}

void mxMainWindow::AuxToolsDisassemble1(GenericActionEx<wxString> *on_end) {
	RaiiDeletePtr<GenericActionEx<wxString> > oe_del(on_end);
	IF_THERE_ISNT_SOURCE return;
	
	static wxString last_disassembled_binary;
		
	mxSource *src=CURRENT_SOURCE;
	
	// compose required command
	wxString out_fname = DIR_PLUS_FILE(config->temp_dir,"objdump.txt"), in_fname;	
	if (project) {
		project_file_item *pi = project->FindFromItem(src->treeId);
		if (pi && pi->where==FT_SOURCE) {
			in_fname = src->GetBinaryFileName().GetFullPath();
		} else {
			in_fname = project->GetExePath();
		}
	} else {
		in_fname = src->GetBinaryFileName().GetFullPath();
	}
	
	if (last_disassembled_binary!=in_fname || !wxFileName::FileExists(out_fname) ||
			wxFileName(in_fname).GetModificationTime()>wxFileName(out_fname).GetModificationTime() ) 
	{ // si tiene que correr antes objdump

#ifdef __WIN32__
		wxString command("cmd /c \"objdump -d -C -l ");
#else
		wxString command("sh -c \'objdump -d -C -l ");
#endif
		command << mxUT::Quotize(in_fname) << " >" << mxUT::Quotize(out_fname);
#ifdef __WIN32__
		command << "\"";
#else
		command << "\'";
#endif

		// grab all arguments the lambda will need
		wxString *plast_disassembled_binary=&last_disassembled_binary; // to be used in _CAPTURELIST_4
		_CAPTURELIST_4( s_lmbDasm2oe,lmb_arg,
			wxString*,plast_disassembled_binary,
			wxString,in_fname, wxString,out_fname,
			GenericActionEx<wxString>*,on_end );
		on_end=nullptr; // avoid double delete, the lambda will do it now
		
		// define the lambda for after running the commmand
		_LAMBDAEX_1( lmbDasm2oe, int,retval, s_lmbDasm2oe,args, {
			if (retval==0) {
				(*(args.plast_disassembled_binary))=args.in_fname; // for memoization
				args.on_end->Do(args.out_fname); delete args.on_end; 
			}
			else { wxArrayString aux; showExternToolErrorMessage(retval, aux, "objdump"); } 
		} );
		
		// finally launch the process and show the osd message
		mxOSD::Execute( command, LANG(OSD_DISASSEMBLING,"Desensamblando..."), new lmbDasm2oe(lmb_arg) );

	} else // si no necesita volver a correr objdump
		on_end->Do(out_fname); 
}

void mxMainWindow::OnToolsDisassembleOfflineFunc (wxCommandEvent & event) {
	IF_THERE_IS_SOURCE {
		// first, try to select the whole scope
		mxSource *src = CURRENT_SOURCE;
		int ss;
		if ( !src->FindScope(src->GetCurrentPos(),nullptr,true,&ss,true).IsEmpty()
			 && src->LineFromPosition(ss)<src->GetCurrentLine() ) 
		{
			src->SetCurrentPos(src->PositionFromLine( src->LineFromPosition(ss)+1 ));
		}
		src->mxSource::OnBraceMatch(event);
		// then run objdump
		_LAMBDAEX_0( lmbDasmFunc2, wxString,out_fname,
			{ main_window->AuxToolsDisassemble2(out_fname,true); } 
		);
		AuxToolsDisassemble1( new lmbDasmFunc2() );
	};
}

void mxMainWindow::OnToolsDisassembleOfflineSel (wxCommandEvent & event) {
	_LAMBDAEX_0( lmbDasmFunc2, wxString,out_fname, 
		{ main_window->AuxToolsDisassemble2(out_fname,false); } 
	);
	AuxToolsDisassemble1( new lmbDasmFunc2() );
}

// aux class for mxMainWindow::AuxToolsDisassemble2
struct auxObjdumpLine { 
	wxString s; bool f; 
	auxObjdumpLine() {}
	auxObjdumpLine(wxString _s, bool _f):s(_s),f(_f){} 
};

/**
* @param full_scope si es false muestra solo las lineas que correspondan a la selccion,
*                   si es true muestra completa cualquier funcion que incluya al menos
*                   una de esas lineas
**/
void mxMainWindow::AuxToolsDisassemble2(wxString out_fname, bool full_scope) {
	
	// create and display output panel
	mxStyledOutput *out_dialog  = new mxStyledOutput(main_window,true,false);
	out_dialog->SetZoom(-1);
	out_dialog->SetTabWidth(8);
	aui_manager.AddPane(out_dialog, wxAuiPaneInfo().Float().CloseButton(true).MaximizeButton(true).Resizable(true).Caption("Disassembly (objdump)").BestSize(500,300).Show());
	aui_manager.Update();
	
	// get required source range
	mxSource *src=CURRENT_SOURCE;
	wxString src_fname = src->GetFullPath();
#ifdef __WIN32__
	src_fname.Replace("\\","/");
#endif
	int lfrom = src->LineFromPosition(src->GetSelectionStart());
	int lto = src->LineFromPosition(src->GetSelectionEnd());
	if (lfrom>lto) std::swap(lfrom,lto);
	
	// load and filter results to panel
	vector<auxObjdumpLine> full_scope_dump; // para el full_scope
	wxFileInputStream input(out_fname);
	wxTextInputStream text(input);
	bool on_scope = false; wxString scope_name; // on_scope dice si actualmente estamos o no pasando por el desensamblado de alguna de las lineas que interesan
	
	do {
		wxString line = (input.IsOk() && !input.Eof()) ? text.ReadLine() : ""; // ultima linea ficticia para no tener que repetir código afuera para el último scope
		bool is_asm = line.StartsWith(" ")||line.StartsWith("\t");
		if (line.Len() && !is_asm) {
			if (line.EndsWith(">:")) scope_name = line; // hay lineas "0000004123412 <funcion_que_sigue>:" antes de las que dicen de que linea del fuente vienen
			bool was_on_scope = on_scope;
#ifdef __WIN32__
			wxString rline = line; rline.Replace("\\","/");
#else
			wxString &rline=line;
#endif
			if (rline.StartsWith(src_fname)) { // si indica que lo que sigue corresponde a este fuente, ver que pasa con el nro de linea
				wxString snum = line.Mid(src_fname.Len()).AfterFirst(':');
				if (snum.Contains(' ')) snum = snum.BeforeFirst(' ');
				long lnum=-1;
				on_scope = (on_scope&&full_scope) || (snum.ToLong(&lnum) && lnum-1>=lfrom && lnum-1<=lto); // el -1 por todos lados es por la base 0/1 de la numeración
				if (on_scope||full_scope) { line = wxString("")<<snum<<":\t"<<src->GetLine(lnum-1); line.Replace("\n",""); line.Replace("\r",""); }
			} else {
				if (!full_scope) on_scope=false;
			}
			if (!full_scope && on_scope!=was_on_scope) { // si cambio on_scope...
				if (on_scope) { // ... poner cabecera (funcion que se esta desensamblando)...
					if (!scope_name.IsEmpty()) {
						out_dialog->AppendLine(scope_name,true); 
						scope_name.Clear();
					}
				} else { // ...o dejar espacio (para separar distintas funciones)
					out_dialog->AppendLine("",false); 
					out_dialog->AppendLine("",false); 
				}
			}
		} else if (line.IsEmpty()) {
			if (full_scope_dump.size()) {
				if (on_scope){
					for(size_t i=0;i<full_scope_dump.size();i++) { 
						out_dialog->AppendLine(full_scope_dump[i].s,full_scope_dump[i].f);
					}
					out_dialog->AppendLine("",false); 
					out_dialog->AppendLine("",false); 
				}
			}
			full_scope_dump.clear();
			on_scope=false; scope_name.Clear(); 
		}
		if (full_scope) {
			if (line.Len()) full_scope_dump.push_back(auxObjdumpLine(line,!is_asm));
		} else if (on_scope)
			out_dialog->AppendLine(line,!is_asm);
	} while(input.IsOk() && !input.Eof());
	
}

void mxMainWindow::OnToolsCodeHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("codetools.html");	
}

void mxMainWindow::OnToolsDisassembleHelp(wxCommandEvent &event) {
	mxHelpWindow::ShowHelp("objdump.html");	
}
