/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "build.h"
#include "debugging/debugging.h"

#include <gtk/gtk.h>
#include <map>
#include <list>
#include "stream/stringstream.h"
#include "versionlib.h"

#include "mainframe.h"

typedef std::map<CopiedString, CopiedString> Variables;
Variables g_build_variables;

void build_clear_variables(){
	g_build_variables.clear();
}

void build_set_variable( const char* name, const char* value ){
	g_build_variables[name] = value;
}

const char* build_get_variable( const std::string& name ){
	Variables::iterator i = g_build_variables.find( name.c_str() );
	if ( i != g_build_variables.end() ) {
		return ( *i ).second.c_str();
	}
	globalErrorStream() << "undefined build variable: " << makeQuoted( name.c_str() ) << "\n";
	return "";
}

#include "xml/ixml.h"
#include "xml/xmlelement.h"

class Evaluatable
{
public:
virtual ~Evaluatable() = default;
virtual std::string evaluate() = 0;
virtual void exportXML( XMLImporter& importer ) = 0;
};

class VariableString : public Evaluatable
{
std::string m_string;
public:
VariableString() : m_string(){
}
VariableString( std::string string ) : m_string( std::move(string) ){
}
const char* c_str() const {
	return m_string.c_str();
}
void setString( const std::string& string ){
	m_string = string;
}
std::string evaluate(){
	// replace ".[ExecutableType]" with "[ExecutableExt]"
	{
		size_t pos;
		const std::string pattern = ".[ExecutableType]";
		while ( ( pos = m_string.find(pattern) ) != std::string::npos ) {
			m_string.replace(pos, pattern.length(), "[ExecutableExt]");
		}
	}

	// add missing [ExtraQ3map2Args] if "[RadiantPath]q3map2[ExecutableExt]"
	{
		size_t pos;
		const std::string pattern = "\"[RadiantPath]q3map2[ExecutableExt]\"";
		const std::string extra = "[ExtraQ3map2Args]";
		if ( ( pos = m_string.find(pattern) ) != std::string::npos
				&& m_string.find(extra) == std::string::npos )
		{
			m_string.insert(pos + pattern.size(), " ");
			m_string.insert(pos + pattern.size() + 1, extra);
		}
	}

	std::string output;
	std::string variable_name;
	bool in_variable = false;
	for ( const char c : m_string )
	{
		if ( !in_variable ) {
			switch ( c )
			{
			case '[':
				in_variable = true;
				break;
			default:
				output += c;
				break;
			}
		}
		else
		{
			switch ( c )
			{
			case ']':
				in_variable = false;
				output += build_get_variable( variable_name );
				variable_name.clear();
				break;
			default:
				variable_name += c;
				break;
			}
		}
	}

	return output;
}
void exportXML( XMLImporter& importer ){
	importer << c_str();
}
};

class Conditional : public Evaluatable
{
VariableString* m_test;
public:
Evaluatable* m_result;
Conditional( VariableString* test ) : m_test( test ){
}
~Conditional(){
	delete m_test;
	delete m_result;
}
std::string evaluate(){
	std::string result = m_test->evaluate();
	if ( result.empty() ) {
		return result;
	}
	return m_result->evaluate();
}
void exportXML( XMLImporter& importer ){
	StaticElement conditionElement( "cond" );
	conditionElement.insertAttribute( "value", m_test->c_str() );
	importer.pushElement( conditionElement );
	m_result->exportXML( importer );
	importer.popElement( conditionElement.name() );
}
};

typedef std::vector<Evaluatable*> Evaluatables;

class Tool : public Evaluatable
{
Evaluatables m_evaluatables;
public:
~Tool(){
	for ( Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i )
	{
		delete ( *i );
	}
}
void push_back( Evaluatable* evaluatable ){
	m_evaluatables.push_back( evaluatable );
}
std::string evaluate(){
	std::string result;
	for ( Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i )
	{
		result += ( *i )->evaluate();
	}
	return result;
}
void exportXML( XMLImporter& importer ){
	for ( Evaluatables::iterator i = m_evaluatables.begin(); i != m_evaluatables.end(); ++i )
	{
		( *i )->exportXML( importer );
	}
}
};

#include "xml/ixml.h"

class XMLElementParser : public TextOutputStream
{
public:
virtual ~XMLElementParser() = default;
virtual XMLElementParser& pushElement( const XMLElement& element ) = 0;
virtual void popElement( const char* name ) = 0;
};

class VariableStringXMLConstructor : public XMLElementParser
{
std::string m_buffer;
VariableString& m_variableString;
public:
VariableStringXMLConstructor( VariableString& variableString ) : m_variableString( variableString ){
}
~VariableStringXMLConstructor(){
	m_variableString.setString( std::move(m_buffer) );
}
std::size_t write( const char* buffer, std::size_t length ){
	m_buffer.append( buffer, length );
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	ERROR_MESSAGE( "parse error: invalid element \"" << element.name() << "\"" );
	return *this;
}
void popElement( const char* name ){
}
};

class ConditionalXMLConstructor : public XMLElementParser
{
std::string m_buffer;
Conditional& m_conditional;
public:
ConditionalXMLConstructor( Conditional& conditional ) : m_conditional( conditional ){
}
~ConditionalXMLConstructor(){
	m_conditional.m_result = new VariableString( std::move( m_buffer ) );
}
std::size_t write( const char* buffer, std::size_t length ){
	m_buffer.append( buffer, length );
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	ERROR_MESSAGE( "parse error: invalid element \"" << element.name() << "\"" );
	return *this;
}
void popElement( const char* name ){
}
};

class ToolXMLConstructor : public XMLElementParser
{
std::string m_buffer;
Tool& m_tool;
ConditionalXMLConstructor* m_conditional;
public:
ToolXMLConstructor( Tool& tool ) : m_tool( tool ){
}
~ToolXMLConstructor(){
	flush();
}
std::size_t write( const char* buffer, std::size_t length ){
	m_buffer.append( buffer, length );
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	if ( string_equal( element.name(), "cond" ) ) {
		flush();
		Conditional* conditional = new Conditional( new VariableString( element.attribute( "value" ) ) );
		m_tool.push_back( conditional );
		m_conditional = new ConditionalXMLConstructor( *conditional );
		return *m_conditional;
	}
	else
	{
		ERROR_MESSAGE( "parse error: invalid element \"" << element.name() << "\"" );
		return *this;
	}
}
void popElement( const char* name ){
	if ( string_equal( name, "cond" ) ) {
		delete m_conditional;
	}
}

void flush(){
	if ( !m_buffer.empty() ) {
		m_tool.push_back( new VariableString( std::move( m_buffer ) ) );
		m_buffer.clear();
	}
}
};

typedef VariableString BuildCommand;
typedef std::list<BuildCommand> Build;

class BuildXMLConstructor : public XMLElementParser
{
VariableStringXMLConstructor* m_variableString;
Build& m_build;
public:
BuildXMLConstructor( Build& build ) : m_build( build ){
}
std::size_t write( const char* buffer, std::size_t length ){
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	if ( string_equal( element.name(), "command" ) ) {
		m_build.push_back( BuildCommand() );
		m_variableString = new VariableStringXMLConstructor( m_build.back() );
		return *m_variableString;
	}
	else
	{
		ERROR_MESSAGE( "parse error: invalid element" );
		return *this;
	}
}
void popElement( const char* name ){
	delete m_variableString;
}
};

typedef std::pair<CopiedString, Build> BuildPair;
const char *SEPARATOR_STRING = "-";
static bool is_separator( const BuildPair &p ){
	if ( !string_equal( p.first.c_str(), SEPARATOR_STRING ) ) {
		return false;
	}
	for ( Build::const_iterator j = p.second.begin(); j != p.second.end(); ++j )
	{
		if ( !string_equal( ( *j ).c_str(), "" ) ) {
			return false;
		}
	}
	return true;
}


typedef std::list<BuildPair> Project;

Project::iterator Project_find( Project& project, const char* name ){
	return std::find_if(project.begin(), project.end(), [&](const BuildPair &self) {
		return string_equal(self.first.c_str(), name);
	});
}

Project::iterator Project_find( Project& project, std::size_t index ){
	Project::iterator i = project.begin();
	while ( index-- != 0 && i != project.end() )
	{
		++i;
	}
	return i;
}

Build& project_find( Project& project, const char* build ){
	Project::iterator i = Project_find( project, build );
	ASSERT_MESSAGE( i != project.end(), "error finding build command" );
	return ( *i ).second;
}

Build::iterator Build_find( Build& build, std::size_t index ){
	Build::iterator i = build.begin();
	while ( index-- != 0 && i != build.end() )
	{
		++i;
	}
	return i;
}

typedef std::map<CopiedString, Tool> Tools;

class ProjectXMLConstructor : public XMLElementParser
{
ToolXMLConstructor* m_tool;
BuildXMLConstructor* m_build;
Project& m_project;
Tools& m_tools;
public:
ProjectXMLConstructor( Project& project, Tools& tools ) : m_project( project ), m_tools( tools ){
}
std::size_t write( const char* buffer, std::size_t length ){
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	if ( string_equal( element.name(), "var" ) ) {
		Tools::iterator i = m_tools.insert( Tools::value_type( element.attribute( "name" ), Tool() ) ).first;
		m_tool = new ToolXMLConstructor( ( *i ).second );
		return *m_tool;
	}
	else if ( string_equal( element.name(), "build" ) ) {
		m_project.push_back( Project::value_type( element.attribute( "name" ), Build() ) );
		m_build = new BuildXMLConstructor( m_project.back().second );
		return *m_build;
	}
	else if ( string_equal( element.name(), "separator" ) ) {
		m_project.push_back( Project::value_type( SEPARATOR_STRING, Build() ) );
		return *this;
	}
	else
	{
		ERROR_MESSAGE( "parse error: invalid element" );
		return *this;
	}
}
void popElement( const char* name ){
	if ( string_equal( name, "var" ) ) {
		delete m_tool;
	}
	else if ( string_equal( name, "build" ) ) {
		delete m_build;
	}
}
};

class SkipAllParser : public XMLElementParser
{
public:
std::size_t write( const char* buffer, std::size_t length ){
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	return *this;
}
void popElement( const char* name ){
}
};

class RootXMLConstructor : public XMLElementParser
{
CopiedString m_elementName;
XMLElementParser& m_parser;
SkipAllParser m_skip;
Version m_version;
bool m_compatible;
public:
RootXMLConstructor( const char* elementName, XMLElementParser& parser, const char* version ) :
	m_elementName( elementName ),
	m_parser( parser ),
	m_version( version_parse( version ) ),
	m_compatible( false ){
}
std::size_t write( const char* buffer, std::size_t length ){
	return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
	if ( string_equal( element.name(), m_elementName.c_str() ) ) {
		Version dataVersion( version_parse( element.attribute( "version" ) ) );
		if ( version_compatible( m_version, dataVersion ) ) {
			m_compatible = true;
			return m_parser;
		}
		else
		{
			return m_skip;
		}
	}
	else
	{
		//ERROR_MESSAGE("parse error: invalid element \"" << element.name() << "\"");
		return *this;
	}
}
void popElement( const char* name ){
}

bool versionCompatible() const {
	return m_compatible;
}
};

namespace
{
Project g_build_project;
Tools g_build_tools;
bool g_build_changed = false;
}

void build_error_undefined_tool( const char* build, const char* tool ){
	globalErrorStream() << "build " << makeQuoted( build ) << " refers to undefined tool " << makeQuoted( tool ) << '\n';
}

void project_verify( Project& project, Tools& tools ){
#if 0
	for ( Project::iterator i = project.begin(); i != project.end(); ++i )
	{
		Build& build = ( *i ).second;
		for ( Build::iterator j = build.begin(); j != build.end(); ++j )
		{
			Tools::iterator k = tools.find( ( *j ).first );
			if ( k == g_build_tools.end() ) {
				build_error_undefined_tool( ( *i ).first.c_str(), ( *j ).first.c_str() );
			}
		}
	}
#endif
}

void build_run( const char* name, CommandListener& listener ){
	for ( Tools::iterator i = g_build_tools.begin(); i != g_build_tools.end(); ++i )
	{
		std::string output = ( *i ).second.evaluate();
		build_set_variable( ( *i ).first.c_str(), output.c_str() );
	}

	{
		Project::iterator i = Project_find( g_build_project, name );
		if ( i != g_build_project.end() ) {
			Build& build = ( *i ).second;
			for ( Build::iterator j = build.begin(); j != build.end(); ++j )
			{
				std::string output = ( *j ).evaluate();
				listener.execute( output.c_str() );
			}
		}
		else
		{
			globalErrorStream() << "build " << makeQuoted( name ) << " not defined";
		}
	}
}


typedef std::vector<XMLElementParser*> XMLElementStack;

class XMLParser : public XMLImporter
{
XMLElementStack m_stack;
public:
XMLParser( XMLElementParser& parser ){
	m_stack.push_back( &parser );
}
std::size_t write( const char* buffer, std::size_t length ){
	return m_stack.back()->write( buffer, length );
}
void pushElement( const XMLElement& element ){
	m_stack.push_back( &m_stack.back()->pushElement( element ) );
}
void popElement( const char* name ){
	m_stack.pop_back();
	m_stack.back()->popElement( name );
}
};

#include "stream/textfilestream.h"
#include "xml/xmlparser.h"

const char* const BUILDMENU_VERSION = "2.0";

bool build_commands_parse( const char* filename ){
	TextFileInputStream projectFile( filename );
	if ( !projectFile.failed() ) {
		ProjectXMLConstructor projectConstructor( g_build_project, g_build_tools );
		RootXMLConstructor rootConstructor( "project", projectConstructor, BUILDMENU_VERSION );
		XMLParser importer( rootConstructor );
		XMLStreamParser parser( projectFile );
		parser.exportXML( importer );

		if ( rootConstructor.versionCompatible() ) {
			project_verify( g_build_project, g_build_tools );

			return true;
		}
		globalErrorStream() << "failed to parse build menu: " << makeQuoted( filename ) << "\n";
	}
	return false;
}

void build_commands_clear(){
	g_build_project.clear();
	g_build_tools.clear();
}

class BuildXMLExporter
{
Build& m_build;
public:
BuildXMLExporter( Build& build ) : m_build( build ){
}
void exportXML( XMLImporter& importer ){
	importer << "\n";
	for ( Build::iterator i = m_build.begin(); i != m_build.end(); ++i )
	{
		StaticElement commandElement( "command" );
		importer.pushElement( commandElement );
		( *i ).exportXML( importer );
		importer.popElement( commandElement.name() );
		importer << "\n";
	}
}
};

class ProjectXMLExporter
{
Project& m_project;
Tools& m_tools;
public:
ProjectXMLExporter( Project& project, Tools& tools ) : m_project( project ), m_tools( tools ){
}
void exportXML( XMLImporter& importer ){
	StaticElement projectElement( "project" );
	projectElement.insertAttribute( "version", BUILDMENU_VERSION );
	importer.pushElement( projectElement );
	importer << "\n";

	for ( Tools::iterator i = m_tools.begin(); i != m_tools.end(); ++i )
	{
		StaticElement toolElement( "var" );
		toolElement.insertAttribute( "name", ( *i ).first.c_str() );
		importer.pushElement( toolElement );
		( *i ).second.exportXML( importer );
		importer.popElement( toolElement.name() );
		importer << "\n";
	}
	for ( Project::iterator i = m_project.begin(); i != m_project.end(); ++i )
	{
		if ( is_separator( *i ) ) {
			StaticElement buildElement( "separator" );
			importer.pushElement( buildElement );
			importer.popElement( buildElement.name() );
			importer << "\n";
		}
		else
		{
			StaticElement buildElement( "build" );
			buildElement.insertAttribute( "name", ( *i ).first.c_str() );
			importer.pushElement( buildElement );
			BuildXMLExporter buildExporter( ( *i ).second );
			buildExporter.exportXML( importer );
			importer.popElement( buildElement.name() );
			importer << "\n";
		}
	}
	importer.popElement( projectElement.name() );
}
};

#include "xml/xmlwriter.h"

void build_commands_write( const char* filename ){
	TextFileOutputStream projectFile( filename );
	if ( !projectFile.failed() ) {
		XMLStreamWriter writer( projectFile );
		ProjectXMLExporter projectExporter( g_build_project, g_build_tools );
		writer << "\n";
		projectExporter.exportXML( writer );
		writer << "\n";
	}
}


#include <gdk/gdkkeysyms.h>

#include "gtkutil/dialog.h"
#include "gtkutil/closure.h"
#include "gtkutil/window.h"
#include "gtkdlgs.h"

void Build_refreshMenu( ui::Menu menu );


void BSPCommandList_Construct( ui::ListStore store, Project& project ){
	store.clear();

	for ( Project::iterator i = project.begin(); i != project.end(); ++i )
	{
		store.append(0, (*i).first.c_str());
	}

	store.append();
}

class ProjectList
{
public:
Project& m_project;
ui::ListStore m_store{ui::null};
GtkWidget* m_buildView;
bool m_changed;
ProjectList( Project& project ) : m_project( project ), m_changed( false ){
}
};

gboolean project_cell_edited(ui::CellRendererText cell, gchar* path_string, gchar* new_text, ProjectList* projectList ){
	Project& project = projectList->m_project;

	auto path = ui::TreePath( path_string );

	ASSERT_MESSAGE( gtk_tree_path_get_depth( path ) == 1, "invalid path length" );

	GtkTreeIter iter;
	gtk_tree_model_get_iter(projectList->m_store, &iter, path );

	Project::iterator i = Project_find( project, gtk_tree_path_get_indices( path )[0] );
	if ( i != project.end() ) {
		projectList->m_changed = true;
		if ( string_empty( new_text ) ) {
			project.erase( i );
			gtk_list_store_remove( projectList->m_store, &iter );
		}
		else
		{
			( *i ).first = new_text;
			gtk_list_store_set( projectList->m_store, &iter, 0, new_text, -1 );
		}
	}
	else if ( !string_empty( new_text ) ) {
		projectList->m_changed = true;
		project.push_back( Project::value_type( new_text, Build() ) );

		gtk_list_store_set( projectList->m_store, &iter, 0, new_text, -1 );
		projectList->m_store.append();
		//make command field activatable
		g_signal_emit_by_name( G_OBJECT( gtk_tree_view_get_selection( GTK_TREE_VIEW( projectList->m_buildView ) ) ), "changed" );
	}

	gtk_tree_path_free( path );

	Build_refreshMenu( g_bsp_menu );

	return FALSE;
}

gboolean project_key_press( ui::TreeView widget, GdkEventKey* event, ProjectList* projectList ){
	Project& project = projectList->m_project;

	if ( event->keyval == GDK_KEY_Delete ) {
		auto selection = ui::TreeSelection::from(gtk_tree_view_get_selection(widget));
		GtkTreeIter iter;
		GtkTreeModel* model;
		if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
			auto path = gtk_tree_model_get_path( model, &iter );
			Project::iterator x = Project_find( project, gtk_tree_path_get_indices( path )[0] );
			gtk_tree_path_free( path );

			if ( x != project.end() ) {
				projectList->m_changed = true;
				project.erase( x );
				Build_refreshMenu( g_bsp_menu );

				gtk_list_store_remove( projectList->m_store, &iter );
			}
		}
	}
	return FALSE;
}


Build* g_current_build = 0;

gboolean project_selection_changed( ui::TreeSelection selection, ui::ListStore store ){
	Project& project = g_build_project;

	store.clear();

	GtkTreeIter iter;
	GtkTreeModel* model;
	if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
		auto path = gtk_tree_model_get_path( model, &iter );
		Project::iterator x = Project_find( project, gtk_tree_path_get_indices( path )[0] );
		gtk_tree_path_free( path );

		if ( x != project.end() ) {
			Build& build = ( *x ).second;
			g_current_build = &build;

			for ( Build::iterator i = build.begin(); i != build.end(); ++i )
			{
				store.append(0, (*i).c_str());
			}
			store.append();
		}
		else
		{
			g_current_build = 0;
		}
	}
	else
	{
		g_current_build = 0;
	}

	return FALSE;
}

gboolean commands_cell_edited(ui::CellRendererText cell, const gchar* path_string, const gchar* new_text, ui::ListStore store ){
	if ( g_current_build == 0 ) {
		return FALSE;
	}
	Build& build = *g_current_build;

	auto path = ui::TreePath( path_string );

	ASSERT_MESSAGE( gtk_tree_path_get_depth( path ) == 1, "invalid path length" );

	GtkTreeIter iter;
	gtk_tree_model_get_iter(store, &iter, path );

	Build::iterator i = Build_find( build, gtk_tree_path_get_indices( path )[0] );
	if ( i != build.end() ) {
		g_build_changed = true;
		( *i ).setString( new_text );

		gtk_list_store_set( store, &iter, 0, new_text, -1 );
	}
	else if ( !string_empty( new_text ) ) {
		g_build_changed = true;
		build.push_back( Build::value_type( VariableString( new_text ) ) );

		gtk_list_store_set( store, &iter, 0, new_text, -1 );

		store.append();
	}

	gtk_tree_path_free( path );

	Build_refreshMenu( g_bsp_menu );

	return FALSE;
}

gboolean commands_key_press( ui::TreeView widget, GdkEventKey* event, ui::ListStore store ){
	if ( g_current_build == 0 ) {
		return FALSE;
	}
	Build& build = *g_current_build;

	if ( event->keyval == GDK_KEY_Delete ) {
		auto selection = gtk_tree_view_get_selection(widget );
		GtkTreeIter iter;
		GtkTreeModel* model;
		if ( gtk_tree_selection_get_selected( selection, &model, &iter ) ) {
			auto path = gtk_tree_model_get_path( model, &iter );
			Build::iterator i = Build_find( build, gtk_tree_path_get_indices( path )[0] );
			gtk_tree_path_free( path );

			if ( i != build.end() ) {
				g_build_changed = true;
				build.erase( i );

				gtk_list_store_remove( store, &iter );
			}
		}
	}
	return FALSE;
}


ui::Window BuildMenuDialog_construct( ModalDialog& modal, ProjectList& projectList ){
	ui::Window window = MainFrame_getWindow().create_dialog_window("Build Menu", G_CALLBACK(dialog_delete_callback ), &modal, -1, 400 );

	// FIXME: GTK_WIN_POS_CENTER_ON_PARENT must be used instead but does not work
	// for unknown reason.
	// It's possible MaingFrame_getWindow() does not return the main window.
	// It's known the preferences window has same issue when using MaingFrame_getWindow().
	gtk_window_set_position( window, GTK_WIN_POS_CENTER_ALWAYS );

	{
		auto table1 = create_dialog_table( 2, 2, 4, 4, 4 );
		window.add(table1);
		{
			auto vbox = create_dialog_vbox( 4 );
            table1.attach(vbox, {1, 2, 0, 1}, {GTK_FILL, GTK_FILL});
			{
				auto button = create_dialog_button( "OK", G_CALLBACK( dialog_button_ok ), &modal );
				vbox.pack_start( button, FALSE, FALSE, 0 );
			}
			{
				auto button = create_dialog_button( "Cancel", G_CALLBACK( dialog_button_cancel ), &modal );
				vbox.pack_start( button, FALSE, FALSE, 0 );
			}
		}
		auto buildViewStore = ui::ListStore::from(gtk_list_store_new( 1, G_TYPE_STRING ));
		auto buildView = ui::TreeView( ui::TreeModel::from( buildViewStore._handle ));
		{
			auto frame = create_dialog_frame( "Build menu" );
            table1.attach(frame, {0, 1, 0, 1});
			{
				auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
				frame.add(scr);

				{
					auto view = buildView;
					auto store = buildViewStore;
					gtk_tree_view_set_headers_visible(view, FALSE );

					auto renderer = ui::CellRendererText(ui::New);
					object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
					renderer.connect("edited", G_CALLBACK( project_cell_edited ), &projectList );

					auto column = ui::TreeViewColumn( "", renderer, {{"text", 0}} );
					gtk_tree_view_append_column(view, column );

					auto selection = gtk_tree_view_get_selection(view );
					gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );

					view.show();

					projectList.m_buildView = buildView;
					projectList.m_store = store;
					scr.add(view);

					view.connect( "key_press_event", G_CALLBACK( project_key_press ), &projectList );

					store.unref();
				}
			}
		}
		{
			auto frame = create_dialog_frame( "Command line" );
            table1.attach(frame, {0, 1, 1, 2});
			{
				auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
				frame.add(scr);

				{
					auto store = ui::ListStore::from(gtk_list_store_new( 1, G_TYPE_STRING ));

					auto view = ui::TreeView(ui::TreeModel::from( store._handle ));
					gtk_tree_view_set_headers_visible(view, FALSE );

					auto renderer = ui::CellRendererText(ui::New);
					object_set_boolean_property( G_OBJECT( renderer ), "editable", TRUE );
					g_object_set( G_OBJECT( renderer ), "wrap-mode", PANGO_WRAP_WORD, NULL );
					//g_object_set( G_OBJECT( renderer ), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, NULL );
					object_set_int_property( G_OBJECT( renderer ), "wrap-width", 640 );
					renderer.connect( "edited", G_CALLBACK( commands_cell_edited ), store );

					auto column = ui::TreeViewColumn( "", renderer, {{"text", 0}} );
					gtk_tree_view_append_column(view, column );

					auto selection = gtk_tree_view_get_selection(view );
					gtk_tree_selection_set_mode( selection, GTK_SELECTION_BROWSE );

					view.show();

					scr.add(view);

					store.unref();

					view.connect( "key_press_event", G_CALLBACK( commands_key_press ), store );

					auto sel = ui::TreeSelection::from(gtk_tree_view_get_selection(buildView ));
					sel.connect( "changed", G_CALLBACK( project_selection_changed ), store );
				}
			}
		}
	}

	BSPCommandList_Construct( projectList.m_store, g_build_project );

	return window;
}

namespace
{
CopiedString g_buildMenu;
CopiedString g_lastExecutedBuild;
}

void LoadBuildMenu();

void DoBuildMenu(){
	ModalDialog modal;

	ProjectList projectList( g_build_project );

	ui::Window window = BuildMenuDialog_construct( modal, projectList );

	if ( modal_dialog_show( window, modal ) == eIDCANCEL ) {
		build_commands_clear();
		LoadBuildMenu();

		Build_refreshMenu( g_bsp_menu );
	}
	else if ( projectList.m_changed ) {
		g_build_changed = true;
	}

	window.destroy();
}



#include "gtkutil/menu.h"
#include "mainframe.h"
#include "preferences.h"
#include "qe3.h"

class BuildMenuItem
{
const char* m_name;
public:
ui::MenuItem m_item;
BuildMenuItem( const char* name, ui::MenuItem item )
	: m_name( name ), m_item( item ){
}
void run(){
	g_lastExecutedBuild = m_name;
	RunBSP( m_name );
}
typedef MemberCaller<BuildMenuItem, void(), &BuildMenuItem::run> RunCaller;
};

typedef std::list<BuildMenuItem> BuildMenuItems;
BuildMenuItems g_BuildMenuItems;


ui::Menu g_bsp_menu{ui::null};

void Build_constructMenu( ui::Menu menu ){
	for ( Project::iterator i = g_build_project.begin(); i != g_build_project.end(); ++i )
	{
		g_BuildMenuItems.push_back( BuildMenuItem( ( *i ).first.c_str(), ui::MenuItem(ui::null) ) );
		if ( is_separator( *i ) ) {
			g_BuildMenuItems.back().m_item = menu_separator( menu );
		}
		else
		{
			g_BuildMenuItems.back().m_item = create_menu_item_with_mnemonic( menu, ( *i ).first.c_str(), BuildMenuItem::RunCaller( g_BuildMenuItems.back() ) );
		}
	}
}


void Build_refreshMenu( ui::Menu menu ){
	for (auto i = g_BuildMenuItems.begin(); i != g_BuildMenuItems.end(); ++i )
	{
		menu.remove(ui::MenuItem(i->m_item));
	}

	g_BuildMenuItems.clear();

	Build_constructMenu( menu );
}


void LoadBuildMenu(){
	if ( string_empty( g_buildMenu.c_str() ) || !build_commands_parse( g_buildMenu.c_str() ) ) {
		{
			StringOutputStream buffer( 256 );
			buffer << GameToolsPath_get() << "default_build_menu.xml";

			bool success = build_commands_parse( buffer.c_str() );
			ASSERT_MESSAGE( success, "failed to parse default build commands: " << buffer.c_str() );
		}
		{
			StringOutputStream buffer( 256 );
			buffer << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/build_menu.xml";

			g_buildMenu = buffer.c_str();
		}
	}
}

void SaveBuildMenu(){
	if ( g_build_changed ) {
		g_build_changed = false;
		build_commands_write( g_buildMenu.c_str() );
	}
}

#include "preferencesystem.h"
#include "stringio.h"

void BuildMenu_Construct(){
	GlobalPreferenceSystem().registerPreference( "BuildMenu", make_property_string( g_buildMenu ) );
	LoadBuildMenu();
}
void BuildMenu_Destroy(){
	SaveBuildMenu();
}


void Build_runRecentExecutedBuild(){
	if( g_lastExecutedBuild.empty() ){
		g_BuildMenuItems.begin()->run();
	}
	else{
		RunBSP( g_lastExecutedBuild.c_str() );
	}
}
