/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/08
// RCS-ID:      $Id: build_svc.cpp,v 1.5 2001/04/21 11:44:03 georgetasker Exp $
// Copyright:   (c) Aleksandras Gluchovas (alex@soften.ktu.lt)
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "minimal.cpp"
    #pragma interface "minimal.cpp"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "build_svc.h"
#include "myutils.h"

#include "wx/xml/xmlres.h"
#include "wx/wspp/glist.h"
#include "wx/wspp/pagectrl.h"

#include "wx/txtstrm.h"
#include "wx/wfstream.h"

#include "wx/log.h"
#include "wx/timer.h"
#include "wx/msgdlg.h"

#include "wx/pmf/lseditor.h"
#include "wx/pmf/pm_flalg.h"
#include "wx/fl/controlbar.h"

// constants

static const wxString& BUILD_WND_NAME = "build";

/***** Implementation for class ErrorPinPainter *****/

enum
{
	ERRROR_PIN_TC = BOOKMARK_PIN_TC + 10,
	WARINING_PIN_TC
};

class ErrorPinPainter : public TPinPainterBase
{
public:
	ErrorPinPainter( int tc = ERRROR_PIN_TC );

	virtual void DrawPin( TPinBase* pPin, wxTextEditorView& view, wxDC& dc, 
						  const wxPoint& pos, const wxSize& dim );

	virtual void GetVisuals( wxColour& fgCol, wxColour& bgCol, char& ch );
};

ErrorPinPainter::ErrorPinPainter( int tc ) 

	: TPinPainterBase( tc )
{
}

void ErrorPinPainter::DrawPin( TPinBase* pPin, wxTextEditorView& view, wxDC& dc, 
						       const wxPoint& pos, const wxSize& dim )
{
	wxColour fg, bg; char ch;
	GetVisuals( fg, bg, ch );

	wxBrush bkBrush( bg, wxSOLID );
	dc.SetPen( *wxBLACK_PEN );
	dc.SetBrush( bkBrush );

	dc.DrawRoundedRectangle( pos.x+2, pos.y, dim.x-4, dim.y, 4 );
	dc.SetTextForeground( fg );
	dc.SetBackgroundMode( wxTRANSPARENT );
	
	wxString text = ch;
	wxCoord w = 0, h = 0;
	dc.GetTextExtent( text, &w, &h );

	dc.DrawText( text,
				 pos.x + ( dim.x - w ) / 2, 
				 pos.y + ( dim.y - h ) / 2 );
}

void ErrorPinPainter::GetVisuals( wxColour& fgCol, 
								  wxColour& bgCol, char& ch )
{
	ch = 'E';
	fgCol = *wxWHITE;
	bgCol = wxColour(196,0,0);
}

class WarningPainter : public ErrorPinPainter
{
public:
	WarningPainter()
		: ErrorPinPainter( WARINING_PIN_TC )
	{}

	virtual void GetVisuals( wxColour& fgCol, wxColour& bgCol, char& ch )
	{
		ch = 'W';
		fgCol = *wxWHITE;
		bgCol = wxColour(0,0,196);
	}
};

/*** Implementation for class BuildTemplateInfo *****/

BuildTemplateInfo::~BuildTemplateInfo()
{
	WX_CLEAR_ARRAY( mArgs );
}

/*** Implementation for class BuildConfiguration *****/

BuildConfiguration::BuildConfiguration()

	: mpTempl( NULL ),
	  mIsActive( FALSE )
{}

/*** Implementation for class BuildConfigurationsDialog *****/

BEGIN_EVENT_TABLE( BuildConfigurationsDialog, wxDialog )

	EVT_BUTTON( XMLID( "new" ),		 BuildConfigurationsDialog::OnNew )
	EVT_BUTTON( XMLID( "edit" ),	 BuildConfigurationsDialog::OnEdit )
	EVT_BUTTON( XMLID( "activate" ), BuildConfigurationsDialog::OnActivate )
	EVT_BUTTON( XMLID( "remove" ),	 BuildConfigurationsDialog::OnRemove )

	EVT_INIT_DIALOG( BuildConfigurationsDialog::OnInitDialog )

END_EVENT_TABLE()

BuildConfigurationsDialog::BuildConfigurationsDialog( BuildConfigurationListT* pConfigs,
													  BuildTemplateInfoListT* pTemplates,
													  wxPMBuildService* buildSvc )

	: mpConfigs( pConfigs ),
	  mpTemplates( pTemplates ),
	  mpBuildSvc( buildSvc )
{}	 

BuildConfigurationsDialog::~BuildConfigurationsDialog()
{}

void BuildConfigurationsDialog::OnNew( wxCommandEvent& event )
{
	BuildConfiguration* newCfg = new BuildConfiguration();

	BuildConfigurationEditDialog dlg( newCfg, "New Build Configuration", *mpTemplates );

	if ( dlg.ShowModal() == wxID_OK )
	{
		dlg.ExchangeValues( FALSE );

		if ( mpConfigs->Count() == 0 )

			newCfg->mIsActive = TRUE;

		mpConfigs->Add( newCfg );

		wxPMBootSvc().GetWorkplace()->GetActiveProject()->Modify( TRUE );
		mpBuildSvc->UpdateFiles();

		RefillList();
	}
	else
		delete newCfg;
}

void BuildConfigurationsDialog::OnEdit( wxCommandEvent& event )
{
	if ( mpList->Number() == 0 )
		return;

	BuildConfiguration* cfg = (*mpConfigs)[ mpList->GetSelection() ];

	BuildConfiguration copy = *cfg;

	BuildConfigurationEditDialog dlg( cfg, "Edit Build Configuration", *mpTemplates );

	if ( dlg.ShowModal() != wxID_OK )
	
		*cfg = copy;
	else
	{
		dlg.ExchangeValues( FALSE );

		wxPMBootSvc().GetWorkplace()->GetActiveProject()->Modify( TRUE );
		mpBuildSvc->UpdateFiles();

		RefillList();
	}
}

void BuildConfigurationsDialog::OnActivate( wxCommandEvent& event )
{
	if ( mpList->Number() == 0 )
		return;

	for( size_t i = 0; i != mpConfigs->Count(); ++i )

		(*mpConfigs)[i]->mIsActive = FALSE;

	(*mpConfigs)[ mpList->GetSelection() ]->mIsActive = TRUE;

	wxPMBootSvc().GetWorkplace()->GetActiveProject()->Modify( TRUE );

	RefillList();
}

void BuildConfigurationsDialog::OnRemove( wxCommandEvent& event )
{
	if ( mpList->Number() == 0 )
		return;

	size_t n = (size_t)mpList->GetSelection();
	bool wasActive = (*mpConfigs)[n]->mIsActive;

	delete (*mpConfigs)[n];
	(*mpConfigs).Remove(n);

	if ( wasActive && mpConfigs->Count() )

		(*mpConfigs)[0u]->mIsActive = TRUE;

	wxPMBootSvc().GetWorkplace()->GetActiveProject()->Modify( TRUE );
	mpBuildSvc->UpdateFiles();

	RefillList();
}

void BuildConfigurationsDialog::OnInitDialog( wxInitDialogEvent& event )
{
	mpList = (wxListBox*)::wxXmlFindWindow( "configs_list", this );

	wxASSERT( mpList ); // DBG::

	RefillList();
}

/*** protected methods ***/

void BuildConfigurationsDialog::RefillList()
{
	if ( mpList->Number() )
		mpList->Clear();

	for( size_t i = 0; i != mpConfigs->Count(); ++i )
	{
		BuildConfiguration& cfg = *(*mpConfigs)[i];

		if ( cfg.mIsActive )
		{
			mpList->Append( "+" + cfg.mName );
			mpList->SetSelection( i );
		}
		else
			mpList->Append( cfg.mName );
	}
}

/*** Implementation for class BuildConfigurationEditDialog *****/

#define TEMPL_CHOICE_ID 300

BEGIN_EVENT_TABLE( BuildConfigurationEditDialog, wxDialog )

	EVT_CHOICE( TEMPL_CHOICE_ID, BuildConfigurationEditDialog::OnChoice )

END_EVENT_TABLE()

BuildConfigurationEditDialog::BuildConfigurationEditDialog( BuildConfiguration* pCfg, 
															const wxString& title,
															BuildTemplateInfoListT& templates )
	: wxDialog( wxPMBootSvc().GetMainWindow(), 
				-1, title, wxDefaultPosition, wxDefaultSize,
				wxSYSTEM_MENU | wxRESIZE_BORDER | wxRESIZE_BOX ),

	  mpCfg( pCfg ),
	  mpTemplates( &templates )
{
	if ( !mpCfg->mpTempl )

		mpCfg->mpTempl = templates[0u];

	RecreateUI();

	ExchangeValues( TRUE );

	Layout();
}

void BuildConfigurationEditDialog::OnChoice( wxCommandEvent& event )
{
	mpCfg->mpTempl = (*mpTemplates)[ mpTempl->GetSelection() ];
	mpCfg->mFields.Clear();
	mpCfg->mValues.Clear();

	RecreateUI();

	ExchangeValues( TRUE );

	Layout();
}

/*** protected methods ***/

void BuildConfigurationEditDialog::RecreateUI()
{
	wxList children;
	
	wxNode* node = GetChildren().First();

	while( node )
	{
		children.Append( (wxWindowBase*)node->GetData() );

		node = node->GetNext();
	}

	node = children.First();

	while( node )
	{
		((wxWindow*)node->GetData())->Show(FALSE);

		this->RemoveChild( (wxWindow*)node->GetData() );

		node = node->GetNext();
	}

	this->SetSizer( NULL );

	wxBoxSizer* box = new wxBoxSizer( wxVERTICAL );

	wxGridSizer* grid = new wxGridSizer( 2 + mpCfg->mpTempl->mArgs.Count(), 2, 5, 5 );

	wxStaticText* nameLabel = new wxStaticText( this,-1, "Name", wxDefaultPosition );
	mpName = new wxTextCtrl( this, -1, "", wxDefaultPosition );

	size_t n = mpTemplates->Count();
	wxString* choices = new wxString[ n ];

	for( size_t k = 0; k != n; ++k )

		choices[k] = (*mpTemplates)[k]->mName;

	wxStaticText* templLabel = new wxStaticText( this,-1, "Template", wxDefaultPosition );
	mpTempl = new wxChoice( this, TEMPL_CHOICE_ID, wxDefaultPosition, wxDefaultSize, n, choices );

	delete [] choices;

	grid->Add( nameLabel );
	grid->Add( mpName );
	grid->Add( templLabel );
	grid->Add( mpTempl );

	BuildArgInfoListT& lst = mpCfg->mpTempl->mArgs;

	for( size_t i = 0; i != lst.Count(); ++i )
	{
		BuildArgInfo& arg = *lst[i];

		wxString txt = arg.mName;

		if ( arg.mValues.GetCount() )
		{
			txt += " (";

			for( size_t k = 0; k != arg.mValues.GetCount(); ++k )
			{
				if ( k != 0 ) 
					txt += ",";

				txt += wxString( arg.mValues[k] );
			}

			txt += ")";
		}
		
		wxString val = arg.mValues.GetCount() ? arg.mValues[0u] : "";

		wxStaticText* name = new wxStaticText( this,-1, txt, wxDefaultPosition );
		wxTextCtrl* value = new wxTextCtrl( this,-1, val, wxDefaultPosition );

		grid->Add( name );
		grid->Add( value );

		arg.mpUserData = (void*)value;
	}

	box->Add( grid, 0, wxALL, 5 );

	wxBoxSizer* btnBox = new wxBoxSizer( wxHORIZONTAL );

	btnBox->Add( new wxButton( this, wxID_OK, "&Ok", wxDefaultPosition ), 0, wxALL, 2 );
	btnBox->Add( new wxButton( this, wxID_CANCEL, "&Cancel", wxDefaultPosition ), 0, wxALL, 2 );

	box->Add( btnBox, 0, wxALL, 5 );

	this->SetAutoLayout( TRUE );
	this->SetSizer( box );

	box->Fit( this );
	box->SetSizeHints( this );

	mpName->SetFocus();

	Center( wxBOTH );
}

void BuildConfigurationEditDialog::ExchangeValues( bool toControls )
{
	if ( toControls )
	{
		for( size_t k = 0; k != mpTemplates->Count(); ++k )
		
			if ( (*mpTemplates)[k] == mpCfg->mpTempl )
			{
				mpTempl->SetSelection(k);
				break;
			}

		for( size_t i = 0; i != mpCfg->mFields.GetCount(); ++i )
		
			((wxTextCtrl*)mpCfg->mpTempl->mArgs[i]->mpUserData)
				->SetValue( mpCfg->mValues[i] );

		mpName->SetValue( mpCfg->mName );
	}
	else
	{
		mpCfg->mFields.Clear();
		mpCfg->mValues.Clear();

		mpCfg->mName = mpName->GetValue();
		mpCfg->mpTempl = (*mpTemplates)[ mpTempl->GetSelection() ];

		for( size_t i = 0; i != mpCfg->mpTempl->mArgs.Count(); ++i )
		{
			BuildArgInfo& arg = *mpCfg->mpTempl->mArgs[i];

			mpCfg->mFields.Add( arg.mName );
			mpCfg->mValues.Add( ((wxTextCtrl*)arg.mpUserData)->GetValue() );
		}
	}
}

/*** Implementation for class PMProjectConfigSet *****/

PMProjectConfigSet::~PMProjectConfigSet()
{
	CleanUp();
}

void PMProjectConfigSet::CleanUp()
{
	WX_CLEAR_ARRAY( mConfigs );
}

void PMProjectConfigSet::AddConfig( BuildConfiguration* cfg )
{
	mConfigs.Add( cfg );
}

void PMProjectConfigSet::RemoveConfig( BuildConfiguration* cfg )
{
	mConfigs.Remove( cfg );
	delete cfg;
}

BuildConfigurationListT& PMProjectConfigSet::GetConfigs()
{
	return mConfigs;
}

BuildConfiguration* PMProjectConfigSet::GetActiveConfig()
{
	for( size_t i = 0; i != mConfigs.Count(); ++i )
	
		if ( mConfigs[i]->mIsActive )

			return mConfigs[i];

	return NULL;
}
/*** Implementation for class BuildScriptTemplateHandler *****/

BuildScriptTemplateHandler::BuildScriptTemplateHandler( wxPMBuildService* buildSvc )

	: BasicScriptProcessorTemplate( buildSvc ),
	  mpBuildSvc( buildSvc )
{}

wxString BuildScriptTemplateHandler::HandleSpecialSubstitution( char* at, ScriptTemplate& templ  )
{
	char* start = at;

	while( *at && *at != '[' && *at != ')' ) ++at;

	wxString tagName;
	ConcatString( tagName, start, size_t( at - start ) );

	if ( tagName == "file_list" )

		return Handle_file_list( at, templ );
	if ( tagName == "script" )

		return Handle_subscript( at, templ );
	else
		return "[ERROR: Unrecognized special-substitution tag: " + tagName + "]";
}

/*** proteced methods ***/

wxString BuildScriptTemplateHandler::Handle_file_list( char* at, ScriptTemplate& templ )
{
	wxString result;
	++at; // skip '['

	if ( *at == '"' ) ++at; // skip starting '"'

	char* start = at; 
	while( *at && *at != ']' && *at != '"' && *at != ',' ) ++at;

	wxString mask;
	ConcatString( mask, start, size_t(at - start) );

	if ( *at == '"' ) ++at;
	++at; // skip ','

	start = at; 
	while( *at && *at != ']' ) ++at;

	wxString script;
	ConcatString( script, start, size_t(at - start) );

	ScriptTemplate* pTempl = mpBuildSvc->FindSubscriptByName( script );

	if ( pTempl == NULL )

		return "[@file_list:ERROR: Requested entry-subscript " + script + " not found!]";

	pTempl->SetParentTemplate( &templ );

	wxPMProject* prj = wxPMBootSvc().GetWorkplace()->GetActiveProject();
	wxASSERT( prj ); // DBG::

	wxString content;

	PMFileInfoListT& files = prj->GetFiles();

	for( size_t i = 0; i != files.Count(); ++i )
	{
		wxString fname = files[i]->GetPath();
		fname = prj->GetRelativePath( fname );

		if ( ::wxMatchWild( mask, fname, FALSE ) )
		{
			pTempl->Reset();
			pTempl->SetVar( "FILE", MakeTmpString( fname ) );

			int extLen = 0;
			int j = fname.Length() - 1;

			while( j >= 0 )
			{
				if ( fname[(unsigned int)j] == '.' )
					break;
				--j;
			}

			if ( j == 0 ) 
				j = fname.Length();

			pTempl->SetVar( "FILE_NO_EXT", 
							MakeTmpString( fname.Mid( 0, j ) ) );

			content += pTempl->SubstitueVariables();
		}
	}

	return content;
}

/*** Implementation for class wxPMBuildService *****/

static wxCommandInfo _gBuildCommands[] = {

	wxCommandInfo( XMLID( "build_build" ),       "&Build", "", "F12" ),
	wxCommandInfo( XMLID( "build_execute" ),     "&Execute", "", "Ctrl+F6" ),
	wxCommandInfo( XMLID( "build_and_execute" ), "Build and &Excecute", "", "F6" ),

	wxCommandInfo( -1, "SEPARATOR" ),
	wxCommandInfo( XMLID( "build_clean" ),       "&Clean", "", "" ),
	wxCommandInfo( XMLID( "build_configure" ),   "Co&nfigure...", "", "Alt+F7" ),

	wxCommandInfo( 0, "" ) // list-terminator
};

static wxCommandInfo _gBuildCommands1[] = {

	wxCommandInfo( XMLID( "build_toggle_output" ), "&Toggle Output Window", "", "Alt+2" ),

	wxCommandInfo( 0, "" ) // terminator
};

static wxPMMergableMenuChain _gBuildChains[] = {

	wxPMMergableMenuChain( "/&Build/", _gBuildCommands ),
	wxPMMergableMenuChain( "/&View/",  _gBuildCommands1 ),

	wxPMMergableMenuChain( "", NULL ) // list-terminator
};

IMPLEMENT_DYNAMIC_CLASS( wxPMBuildService, wxPMPane )

BEGIN_EVENT_TABLE( wxPMBuildService, wxPMPane )

	EVT_BUTTON( XMLID( "build_build" ), wxPMBuildService::OnBuild )
	EVT_BUTTON( XMLID( "build_clean" ), wxPMBuildService::OnClean )
	EVT_BUTTON( XMLID( "build_configure" ), wxPMBuildService::OnConfigure )
	EVT_BUTTON( XMLID( "build_and_execute" ), wxPMBuildService::OnBuildAndExecute )
	EVT_BUTTON( XMLID( "build_execute" ), wxPMBuildService::OnExecute )

	EVT_MENU( XMLID( "build_build" ), wxPMBuildService::OnBuild )
	EVT_MENU( XMLID( "build_clean" ), wxPMBuildService::OnClean )
	EVT_MENU( XMLID( "build_configure" ), wxPMBuildService::OnConfigure )
	EVT_MENU( XMLID( "build_and_execute" ), wxPMBuildService::OnBuildAndExecute )
	EVT_MENU( XMLID( "build_execute" ), wxPMBuildService::OnExecute )
	EVT_MENU( XMLID( "build_toggle_output" ), wxPMBuildService::OnToggleOutputWindow )

	EVT_BUTTON( XMLID( "notify_build_started" ), wxPMBuildService::OnBuildStarted )
	EVT_BUTTON( XMLID( "notify_message_added" ), wxPMBuildService::OnBuildMessageListUpdated )
	EVT_BUTTON( XMLID( "notify_build_finished" ), wxPMBuildService::OnBuildFinished )

	EVT_END_PROCESS ( XMLID( "on_process_terminated" ), wxPMBuildService::OnProcessTerminated )

	EVT_TIMER( 5, wxPMBuildService::OnBuildTimer )

END_EVENT_TABLE()

wxPMBuildService::wxPMBuildService()

	: mpPanel( NULL ),
	  mpBuildProcess( NULL ),
	  mNeedToExecute( FALSE ),
	  mTimer( this, 5 )
{}

wxPMBuildService::~wxPMBuildService()
{}

bool wxPMBuildService::Start()
{
	if ( !mpTopTempl )
	{
		SetPaneName( "Output Window" );

		ScriptTemplate::SetVarPrefixChar( '%' );

		SetProcessorTemplate( new BuildScriptTemplateHandler( this ) );

		wxString sd = wxPMBootSvc().GetStartupDir();

		wxString fname = sd + wxString(wxFILE_SEP_PATH) + "config_build.txt";

		LoadAndProcessConfig( fname );

		wxPMBootSvc().GetDocManager()->AddListener( (wxPMDocumentListener*)this );

		ScriptTemplate::SetVarPrefixChar();

		wxPMBootSvc().RegisterUniqueServiceInstance( this, "I_OutputWindow" );
	}

	return wxPMPane::Start();
}

bool wxPMBuildService::Stop()
{
	if ( mpPanel )

		mpPanel->SetNextHandler( NULL );

	wxPMBootSvc().GetDocManager()->RemoveListener( (wxPMDocumentListener*)this );

	return wxPMPane::Stop();
}

void* wxPMBuildService::GetInterface( const char* infName, const char* version )
{
	if ( strcmp( infName, "I_OutputWindow" ) == 0 )

		return (void*)(I_OutputWindow*)this;
	else
		return NULL;
}

wxPMMergableMenuChain* wxPMBuildService::GetMergableMenuChain()
{
	return _gBuildChains;
}

wxWindow* wxPMBuildService::GetPaneWindow()
{
	return mpPanel;
}

void wxPMBuildService::CreatePaneWindow( wxWindow* pParent )
{
	wxASSERT(pParent); // DBG::	
	mpPanel = wxTheXmlResource->LoadWindow( pParent, wxT("BuildSvc_Panel1") );
	if (!mpPanel)
		return;
	
	mpPanel->SetNextHandler( this );

	mpPageCtrl = (wxPagingCtrl*)wxXmlFindWindow( "output_pages", mpPanel );

	AddOutputWindow( BUILD_WND_NAME, "Build" );
}

static wxPMFrameLayoutPreference _gOutputLayPref( FL_ALIGN_BOTTOM, 400,0, 100, 180, FALSE );

wxPMLayoutPreference* wxPMBuildService::GetLayoutPefs()
{
	if ( !wxPMBootSvc().FindUniqueServiceInstance( "I_PMPaneLayoutAlgorithm" ) )
		return NULL;

	return &_gOutputLayPref;
}

#define BUILD_SECTION_NAME "/Build_Configurations"

bool wxPMBuildService::OnClosingDocument( wxPMDocument& doc, wxOutputStream* s, wxPMSerializer* ser )
{
	if ( !ser || !doc.IsKindOf( CLASSINFO(wxPMProject) ) )
		return TRUE;

	if ( ser->Exists( BUILD_SECTION_NAME ) )
	
		ser->DeleteGroup( BUILD_SECTION_NAME );

	ser->SetPath( BUILD_SECTION_NAME );

	PMProjectConfigSet* cfgSet = DoGetConfigForProject( doc );
	BuildConfigurationListT& lst = cfgSet->GetConfigs();

	for( size_t i = 0; i != lst.Count(); ++i )
	{
		BuildConfiguration* cfg = lst[i];

		ser->SetPath( BUILD_SECTION_NAME + wxString( "/Entry_" + IntToStr( i ) ) );

		ser->Exchange( "build_template", cfg->mpTempl->mName );
		ser->Exchange( "config_name",    cfg->mName );
		ser->Exchange( "is_active",		 cfg->mIsActive );

		for( size_t k = 0; k != cfg->mFields.GetCount(); ++k )
		{
			wxString tmpStr(cfg->mValues[k]);
			ser->Exchange( wxString( cfg->mFields[k] ), tmpStr );
		}
	}

	return TRUE;
}

bool wxPMBuildService::OnOpeningDocument( wxPMDocument& doc, wxInputStream* s, wxPMSerializer* ser )
{
	if ( !ser || !doc.IsKindOf( CLASSINFO(wxPMProject) ) )
		return TRUE;

	if ( ser->Exists( BUILD_SECTION_NAME ) )
	{
		PMProjectConfigSet* cfgSet = DoGetConfigForProject( doc );

		cfgSet->CleanUp();

		ser->SetPath( BUILD_SECTION_NAME );

		long idx = 0; wxString name;
		bool ok = ser->GetFirstGroup( name, idx );

		while( ok )
		{
			ser->SetPath( BUILD_SECTION_NAME + wxString( "/" + name ) );

			BuildConfiguration* cfg = new BuildConfiguration();

			wxString templName;
			
			ser->Exchange( "config_name",    cfg->mName );
			ser->Exchange( "build_template", templName  );
			ser->Exchange( "is_active",      cfg->mIsActive );

			cfg->mpTempl = FindTemplateByName( templName );

			wxASSERT( cfg->mpTempl ); // DBG::

			for( size_t i = 0; i != cfg->mpTempl->mArgs.Count(); ++i )
			{
				BuildArgInfo* argInfo = cfg->mpTempl->mArgs[i];

				cfg->mFields.Add( argInfo->mName );

				wxString value;
				ser->Exchange( argInfo->mName, value );
				cfg->mValues.Add( value );
			}

			cfgSet->AddConfig( cfg );

			ser->SetPath( BUILD_SECTION_NAME );
			ok = ser->GetNextGroup( name, idx );
		}
	}

	return TRUE;
}

bool wxPMBuildService::ProcessSection( Record& rec )
{
	if ( rec.GetName() == "template" )
	{
		BuildTemplateInfo* info = new BuildTemplateInfo();

		info->mName = rec[ "name" ];

		GetStringListVar( rec, "error_filter", info->mErrorFilter );
		GetStringListVar( rec, "warning_filter", info->mWarningFilter );

		info->mContent.SetParentTemplate( mpTopTempl );
		info->mOutputFile.SetParentTemplate( mpTopTempl );
		info->mBuildCmdLine.SetParentTemplate( mpTopTempl );
		info->mCleanCmdLine.SetParentTemplate( mpTopTempl );
		info->mExecCmdLine.SetParentTemplate( mpTopTempl );

		info->mContent.SetScript( rec[ "content" ] );
		info->mOutputFile.SetScript( rec[ "output_file" ] );
		info->mBuildCmdLine.SetScript( rec[ "build_cmdline" ] );
		info->mCleanCmdLine.SetScript( rec[ "clean_cmdline" ] );
		info->mExecCmdLine.SetScript( rec[ "exec_cmdline" ] );

		Record* args = (Record*)rec.FindField( "args" );

		if ( args )
		{
			for( size_t i = 0; i != args->GetFieldCount(); ++i )
			{
				Record& arg = args->GetRecordByIndex(i);

				BuildArgInfo* argInfo = new BuildArgInfo();

				argInfo->mName = arg[0u];

				if ( arg.GetFieldCount() > 1 )
				{
					Record& values = arg.GetRecordByIndex(1);

					for( size_t k = 0; k != values.GetFieldCount(); ++k )

						argInfo->mValues.Add( values[k] );
 			}

				info->mArgs.Add( argInfo );
			}
		}

		mTemplates.Add( info );

	} // end of if( .. )

	return TRUE;
}

void wxPMBuildService::CleanUp()
{
	ScriptProcessor::CleanUp();

	WX_CLEAR_ARRAY( mTemplates );
}

BuildTemplateInfo* wxPMBuildService::FindTemplateByName( const wxString& name )
{
	for( size_t i = 0; i != mTemplates.Count(); ++i )
	
		if ( mTemplates[i]->mName == name )

			return mTemplates[i];

	return NULL;
}

/*** command handlers ***/

void wxPMBuildService::OnBuild( wxCommandEvent& event )
{
	mNeedToExecute = FALSE;
	DoExecute( TRUE );
}

void wxPMBuildService::OnClean( wxCommandEvent& event )
{
	DoExecute( FALSE );
}

void wxPMBuildService::OnBuildAndExecute( wxCommandEvent& event )
{
	mNeedToExecute = TRUE;
	DoExecute( TRUE );
}

void wxPMBuildService::OnExecute( wxCommandEvent& event )
{
	if ( mpBuildProcess )
	{
		wxMessageBox( "Building is already in progress..." );
		return;
	}

	// Do this with ASSERTs because I feel that the button should be
	// Disabled to prevent one of the 2 asserts to occur.
	wxASSERT( &wxPMBootSvc() != NULL );					// DBG::Ugly code... 
			// Can we make IsInit or IsEmpty function to do it more elegant?
	wxASSERT( wxPMBootSvc().GetWorkplace() != NULL );	// DBG::

	wxPMProject* prj = wxPMBootSvc().GetWorkplace()->GetActiveProject();

	PMProjectConfigSet* set = 
		DoGetConfigForProject( *prj );

	BuildConfiguration* cfg = set->GetActiveConfig();
	wxASSERT( cfg ); // DBG::

	ReduceBuildTimeVars( *cfg, *prj );

	long ret = ::wxExecute( cfg->mExecCmdLine, FALSE, NULL );

	if ( !ret )
	{
		wxMessageDialog dlg( wxPMBootSvc().GetMainWindow(),
							 "Executable " + cfg->mExecCmdLine + " does not exist.\n\n Would you like to build it?",
							 "Error",
							 wxYES_NO );

		if ( dlg.ShowModal() == wxID_YES )
		
			OnBuild( event );
	}
	else
		wxPMBootSvc().SetStatusText( "Executing '" + cfg->mExecCmdLine + "'" );
}

void wxPMBuildService::OnToggleOutputWindow( wxCommandEvent& event )
{
	if ( this->IsShown() )

		wxPMBootSvc().HidePane( this );
	else
		wxPMBootSvc().ShowPane( this );
}

void wxPMBuildService::UpdateFiles()
{
	PMProjectConfigSet* set = 
		DoGetConfigForProject( *wxPMBootSvc().GetWorkplace()->GetActiveProject() );

	DoUpdateOutputFiles( set, TRUE );
	DoUpdateOutputFiles( set, FALSE );
}

void wxPMBuildService::DetectMessageInfo( wxString line, OutputMsgInfo& info )
{
	info.mText = line;
	info.mType = OutputMsgInfo::MSG_NORMAL;

	if ( !mpBuildingCfg )
		return;

	line += "\n";
	int len = line.Length();

	for( int i = 0; i != 2; ++i )
	{
		wxStringList* lst = ( i == 0 ) ? &mpBuildingCfg->mpTempl->mErrorFilter
									   : &mpBuildingCfg->mpTempl->mWarningFilter;

		info.mFile = "";
		info.mLine = -1;

		int cur = 0;

		for( size_t k = 0; k != lst->GetCount() && cur < len; ++k )
		{
			wxString expect = (*lst)[k];

			if ( expect.Length() > 2 &&
				 expect[0u] == '_' &&
				 expect[1u] == '_' )
			{
				// extracting not-fixed value string until
				// the occurance of next fixed-value string is encountered

				wxString value;
				wxString next; 
				if ( k < lst->GetCount() )
					next = (*lst)[k+1];

				if ( next.Length() == 0 )
				{
					value = line.Mid( cur );
					cur = len;
				}
				else
				{
					int start = cur;

					while( cur < len )
					{
						if ( line[(unsigned int)cur] == next[0u] )
						{
							int j = 1, pos = cur+1;
							while( j < (int)next.Length() && 
								   pos < len &&
								   line[(unsigned int)pos] == next[(unsigned int)j] )
							{
								++pos; 
								++j;
							}

							if ( j == (int)next.Length() )
							{
								value = line.Mid( start, cur - start );
								cur = pos; 
								++k;
								break; // to outter loop
							}
							else
								++cur; // hasn't matched completely
						}
						else
							++cur;
					}
				}

				if ( value.Length() ) // value found
				{		 
					if ( expect == "__FILE__" )
						info.mFile = value;
					if ( expect == "__LINE__" )
						info.mLine = ::atoi( value );
				}
			}
			else
				// otherwise, just scan for the 'next' as fixed-value 
				// literal which is expected
			{
				while( cur < len )
				{
					if ( line[(unsigned int)cur] == expect[0u] )
					{
						int j = 1, pos = cur+1;
						while( j < (int)expect.Length() && 
							   pos < len &&
							   line[(unsigned int)pos] == expect[(unsigned int)pos] )
						{
							++pos; 
							++j;
						}

						if ( j == (int)expect.Length() )
						{
							cur = pos;
							break;
						}
					}
					else
						++cur;
				}
			}

		}  // end of for(...)

		// finish detection if "expectation-squence" was completed

		if ( info.mFile.Length() &&
			 info.mLine != -1 )
		{
			if ( i == 0 )
				info.mType = OutputMsgInfo::MSG_ERROR;
			else
				info.mType = OutputMsgInfo::MSG_WARNING;

			break;
		}

	} // end of for( 0..1 )
}

void wxPMBuildService::OnConfigure( wxCommandEvent& event )
{
	// Do this with ASSERTs because I feel that the button should be
	// Disabled to prevent one of the 2 asserts to occur.
	// Or what do you think should happen when no Workspace is set?
	wxASSERT( &wxPMBootSvc() != NULL );					// DBG::Ugly code... 
			// Can we make IsInit or IsEmpty function to do it more elegant?
	wxASSERT( wxPMBootSvc().GetWorkplace() != NULL );	// DBG::

	PMProjectConfigSet* cfgSet = 
		DoGetConfigForProject( *wxPMBootSvc().GetWorkplace()->GetActiveProject() );

	BuildConfigurationsDialog dlg( &cfgSet->GetConfigs(), &mTemplates, this );

	wxTheXmlResource->
		LoadDialog( &dlg, wxPMBootSvc().GetMainWindow(), wxT("BuildSvc_ConfigDialog") );

	dlg.Layout();
	dlg.ShowModal();
}

// notification commands received from build-controller thread

void wxPMBuildService::OnBuildStarted( wxCommandEvent& event )
{
}

void wxPMBuildService::OnBuildMessageListUpdated( wxCommandEvent& event )
{
	mpBuildProcess = NULL;
}

void wxPMBuildService::OnBuildFinished( wxCommandEvent& event )
{
}

void wxPMBuildService::OnProcessTerminated( wxProcessEvent& event )
{
	if ( mpBuildProcess )
	{
		mTimer.Stop();

		SuckBuildOutput( FALSE );
		mpBuildProcess = NULL;
		mpBuildingCfg = NULL;
		int errs = 0, wngs = 0;
		
		// calc statistics

		OutputWindowInfo* info = FindOutputWindow( BUILD_WND_NAME );

		for( size_t i = 0; i != info->mMessages.Count(); ++i )
		{
			OutputMsgInfo* msg = info->mMessages[i];

			if ( msg->mType == OutputMsgInfo::MSG_WARNING )
				++wngs;
			else
			if ( msg->mType == OutputMsgInfo::MSG_ERROR )
				++errs;
		}

		wxString s;
		s.Printf( "* Finished - %d error(s), %d warning(s) *", errs, wngs );
		AddMessageLine( "", BUILD_WND_NAME, FALSE );
		AddMessageLine( s, BUILD_WND_NAME, FALSE );

		if ( mNeedToExecute && errs == 0 )
		{
			wxCommandEvent evt;
			OnExecute( evt );
		}
	}
}

void wxPMBuildService::OnBuildTimer( wxTimerEvent& event )
{
	SuckBuildOutput( TRUE );
}

// management of output-windows

void wxPMBuildService::ClearOutputWindow( const wxString& name )
{
	OutputWindowInfo* ownd = FindOutputWindow( name );
	if ( !ownd )
		return;

	WX_CLEAR_ARRAY( ownd->mMessages );

	wxTextEditorModel* model = ownd->mpView->GetModel();
	model->DeleteAllText();

	ownd->mpView->Refresh();
	// TBD::
}

void wxPMBuildService::AddMessageLine( const wxString& line, 
									   const wxString& outWndName,
									   bool detect )
{
	OutputWindowInfo* ownd = FindOutputWindow( outWndName );
	if ( !ownd )
		return;

	OutputMsgInfo* msg = new OutputMsgInfo();

	if ( detect )

		DetectMessageInfo( line, *msg );
	else
	{
		msg->mText = line;
		msg->mLine = -1;
		msg->mType = OutputMsgInfo::MSG_NORMAL;
	}

	// TBD::...
	wxTextEditorModel* model = ownd->mpView->GetModel();
	model->AppendText( msg->mText + "\n" );

	if ( msg->mType == OutputMsgInfo::MSG_ERROR ||
		 msg->mType == OutputMsgInfo::MSG_WARNING )
	{
		int n = model->GetTotalRowCount() - 2;
		model->AddPin( new TPinBase( msg->mType == OutputMsgInfo::MSG_ERROR ? ERRROR_PIN_TC
																		: WARINING_PIN_TC,
									 n ) );
	}

	model->NotifyAllViews();

	ownd->mMessages.Add( msg );

	int last = model->GetTotalRowCount();
	if ( last > 0 )
		model->SetCursor( TPosition( last, 0 ) );
	//ownd->mpView->Refresh();
}

bool wxPMBuildService::AddOutputWindow( const wxString& name, const wxString& displayName )
{
	if ( FindOutputWindow( name ) )
		return FALSE;

	OutputWindowInfo* info = new OutputWindowInfo();

	info->mName = name;
	info->mDisplayName = displayName;

	mOutputWindows.Add( info );

	// TBD::...
	wxTextEditorView* view = new wxTextEditorView( mpPageCtrl, -1, NULL, 0 );
	info->mpView = view;
	view->AddPinPainter( new ErrorPinPainter() );
	view->AddPinPainter( new WarningPainter() );
	wxTextEditorModel* model = view->GetModel();
	view->SetLineTrackingMode( TRUE );
	view->EnableCursor( TRUE );
	model->SetReadOnly( TRUE );
	//model->AppendText( "Hello\nWorld" );
	//model->AddPin( new TPinBase( ERRROR_PIN_TC, 0 ) );
	//model->AddPin( new TPinBase( WARINING_PIN_TC, 1 ) );
	model->NotifyAllViews();

	mpPageCtrl->AddTab( info->mpView, displayName, -1, (wxBitmap*)NULL );

	return TRUE;
}

bool wxPMBuildService::DeleteOutputWindow( const wxString& name )
{
	OutputWindowInfo* ownd = FindOutputWindow( name );

	if ( !ownd )
		return FALSE;

	// TBD::...

	delete ownd;
	mOutputWindows.Remove( ownd );

	return TRUE;
}

OutputWindowInfo* wxPMBuildService::FindOutputWindow( const wxString& outWndName )
{
	for( size_t i = 0; i != mOutputWindows.Count(); ++i )

		if ( mOutputWindows[i]->mName == outWndName )

			return mOutputWindows[i];

	return NULL;
}

/*** protected methods ***/

void wxPMBuildService::SuckBuildOutput( bool fromTiemr )
{
	if ( !mpBuildProcess )
		return;

	wxInputStream* istm = mpBuildProcess->GetInputStream();
	wxInputStream* estm = mpBuildProcess->GetErrorStream();

	for( int i =0; i != 2; ++i )
	{
		wxInputStream* stm = i == 1 ? istm : estm;

		while( stm && !stm->Eof() )
		{
			wxTextInputStream ts( *stm );

			wxString line = ts.ReadLine();

			if ( line.Length() )
					
				AddMessageLine( line, BUILD_WND_NAME, TRUE );
		}
	}
}

void wxPMBuildService::DoExecute( bool build )
{
	if ( mpBuildProcess )
	{
		wxMessageBox( "Building is already in progress..." );
		return;
	}

	// Do this with ASSERTs because I feel that the button should be
	// Disabled to prevent one of the 3 asserts to occur.
	wxASSERT( &wxPMBootSvc() != NULL );					// DBG::Ugly code... 
			// Can we make IsInit or IsEmpty function to do it more elegant?
	wxASSERT( wxPMBootSvc().GetDocManager() != NULL );	// DBG::
	wxASSERT( wxPMBootSvc().GetWorkplace() != NULL );	// DBG::

	// save all opened documents before building (FIXME:: crude?)

	if ( build )
	{
		wxPMBootSvc().GetDocManager()->SaveDocument(
				wxPMBootSvc().GetWorkplace()->GetActiveProject() );
	}

	wxPMProject* prj = wxPMBootSvc().GetWorkplace()->GetActiveProject();

	PMProjectConfigSet* set = 
		DoGetConfigForProject( *prj );

	BuildConfiguration* cfg = set->GetActiveConfig();
	mpBuildingCfg = cfg;
	wxASSERT( cfg ); // DBG::

	ReduceBuildTimeVars( *cfg, *prj );
	ClearOutputWindow( BUILD_WND_NAME );
	OutputWindowInfo* ownd = FindOutputWindow( BUILD_WND_NAME );
	ownd->mpView->SetFocus();

	wxString cmd = build ? cfg->mBuildCmdLine : cfg->mCleanCmdLine;

	//AddMessageLine( "D:\\projects\\wxWorkshop\\ws\\buildsvc.cpp(1193) : error C2001: newline in constant",
	//				  BUILD_WND_NAME, TRUE );

	AddMessageLine( "--------------------Configuration: " + cfg->mName + 
					" --------------------\n" + cmd + "\n", 
					BUILD_WND_NAME, FALSE );

	wxString savedCwd = wxGetCwd();
	::wxSetWorkingDirectory( prj->GetFileInfo()->GetPathOnly() );

	mpBuildProcess = new wxProcess( this, XMLID( "on_process_terminated" ) );
	mpBuildProcess->Redirect();

	long ret = ::wxExecute( cmd, FALSE, mpBuildProcess );

	mTimer.Start( 100 );

	::wxSetWorkingDirectory( savedCwd );
}

void wxPMBuildService::GetStringListVar( Record& fromRec, 
										 const wxString& name, 
										 wxStringList& toLst )
{
	Record& r = fromRec.GetRecordByName( name );

	for( size_t i = 0; i != r.GetFieldCount(); ++i )
	
		toLst.Add( r.GetValueByIndex( i ) );
}

PMProjectConfigSet* wxPMBuildService::DoGetConfigForProject( wxPMDocument& project )
{
	PMProjectConfigSet* set = (PMProjectConfigSet*)GetAssociatedData( project );

	if ( !set )
	{
		set = new PMProjectConfigSet();
		AssociateData( project, set );
	}

	return set;
}

bool wxPMBuildService::GenerateOutputFile( BuildConfiguration& cfg, wxPMProject& project )
{
	ScriptTemplate::SetVarPrefixChar( '%' );

	ReduceBuildTimeVars( cfg, project );

	if ( cfg.mOutputFile.Length() == 0 )
		return TRUE;

	wxString content = cfg.mpTempl->mContent.SubstitueVariables();

	ScriptTemplate::SetVarPrefixChar();

	wxString savedCwd = wxGetCwd();

	::wxSetWorkingDirectory( project.GetFileInfo()->GetPathOnly() );

	wxFileOutputStream fstm( cfg.mOutputFile );
	wxTextOutputStream tstm( fstm );

	tstm << content;

	::wxSetWorkingDirectory( savedCwd );

	return TRUE;
}

void wxPMBuildService::ReduceBuildTimeVars( BuildConfiguration& cfg, wxPMProject& project )
{
	ScriptTemplate::SetVarPrefixChar( '%' );

	ClearTmpStrings();

	ScriptTemplate* templ = GetProcessorTemplate();

	templ->SetVar( "PROJECT_NAME", MakeTmpString( project.GetTitle() ) );
	templ->SetVar( "DATE",		   MakeTmpString( wxDateTime::Now().Format() ) );
	templ->SetVar( "TEMPLATE",	   MakeTmpString( "\"" + cfg.mpTempl->mName + "\"" ) );

	for( size_t i = 0; i != cfg.mFields.GetCount(); ++i )
	
		templ->SetVar( cfg.mFields[i], MakeTmpString( cfg.mValues[i] ) );

	cfg.mOutputFile = cfg.mpTempl->mOutputFile.SubstitueVariables();

	templ->SetVar( "OUTPUT_FILE", MakeTmpString( cfg.mOutputFile ) );

	cfg.mBuildCmdLine = cfg.mpTempl->mBuildCmdLine.SubstitueVariables();

	templ->SetVar( "BUILD_CMDLINE", MakeTmpString( cfg.mBuildCmdLine ) );

	cfg.mCleanCmdLine = cfg.mpTempl->mCleanCmdLine.SubstitueVariables();

	templ->SetVar( "CLEAN_CMDLINE", MakeTmpString( cfg.mCleanCmdLine ) );

	cfg.mExecCmdLine = cfg.mpTempl->mExecCmdLine.SubstitueVariables();

	templ->SetVar( "EXEC_CMDLINE", MakeTmpString( cfg.mExecCmdLine ) );

	ScriptTemplate::SetVarPrefixChar();
}

bool wxPMBuildService::DoUpdateOutputFiles( PMProjectConfigSet* set, bool clean )
{
	// TBD:: error handling 

	BuildConfigurationListT& lst = set->GetConfigs();

	wxPMProject* prj = wxPMBootSvc().GetWorkplace()->GetActiveProject();

	for( size_t i = 0; i != lst.Count(); ++i )
	{
		BuildConfiguration& cfg = *lst[i];

		ReduceBuildTimeVars( cfg, *prj );

		if ( cfg.mOutputFile.Length() )
		{
			if ( wxFileExists( cfg.mOutputFile ) && clean )
			
				wxRemoveFile( cfg.mOutputFile );
			else
			if ( !wxFileExists( cfg.mOutputFile ) && !clean )
				
				GenerateOutputFile( cfg, *prj );
		}
	}

	return TRUE;
}

/***** Implementation for class BuildControllerThread *****/

class BuildProcess : public wxProcess
{
public:
	BuildProcess( BuildControllerThread* cth )
		: 
		  mpCth( cth ) 
	{}

	virtual void OnTerminate(int pid, int status)
	{
		mpCth->OnTerminated();

		//delete this;
	}

protected:
	BuildControllerThread* mpCth;
};

BuildControllerThread::BuildControllerThread( wxPMBuildService* buildSvc, 
											  BuildConfiguration* cfg )

	: mpBuildSvc( buildSvc ),
	  mpCfg( cfg )
{
}

void* BuildControllerThread::Entry()
{
	mIsTerminated = FALSE;

	return NULL;
}

void BuildControllerThread::OnTerminated()
{
	mIsTerminated = TRUE;
}
