/////////////////////////////////////////////////////////////////////////////
// File:        componenthelper.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Copyright:   (C) 2011 Cesar Mauri Loba - CREA Software Systems
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////
#include "sphost/componenthelper.h"
#include "spcore/pin.h"
#include "spcore/language.h"
#include "spcore/paths.h"

#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <stack>
#include <set>
#include <boost/shared_ptr.hpp>

#ifdef WIN32
#include <io.h>
#define access(path,mode) _access(path,mode)
#define R_OK    4               /* Test for read permission.  */
#else
#include <unistd.h>
#endif

#ifdef WIN32
// TODO: remove the following line when boost gets updated
// See: https://svn.boost.org/trac/boost/ticket/4649
#pragma warning (disable:4127)
#endif
#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>

// wx includes
// TODO: give away wx dependency
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/collpane.h>
#include <wx/statbox.h>

#include "widgets_base/containerpanel.h"
#include "widgets_base/containerbook.h"
#include "widgets_base/containercollapsible.h"

using namespace spcore;
using namespace std;
using namespace boost;


#define GETTEXT_DOMAIN_SCRIPTS	"sitplus-sp"

namespace sphost {

/*
	Comments about processing GUI layout.

	Pass the list of strings to the script component and parse during
	GUI construction. Add method to check syntax and semantics 
	without actually building the GUI itself to detect errors ASAP

	An intemediate representation (layout composite) would be great
	when it comes to provide support for more than one GUI toolkit but
	it requires to create an entire class hierarchy.
*/

/**
	Struct to store lines of the script with context
*/
struct LineWithContext
{
	LineWithContext(const string& t, int ln, const string& f) 
	: text(t), linenu(ln), file(f) {}

	string text;
	unsigned int linenu;
	string file;
};

// Throw an error completing information
static
void ThrowError (const string & msg, unsigned int linenu, const char *line, const char* file) {
	string error_msg(msg);

	if (linenu) {
		error_msg+= " line ";
		error_msg+= boost::lexical_cast<std::string>(linenu);
	}
	if (line) {
		error_msg+= "\n";
		error_msg+= line;
	}
	if (file && file[0]!= 0) {
		error_msg+= "\nfile ";
		error_msg+= file;
	}
	throw parser_error(error_msg);
}

static
void ThrowError (const string & msg, const LineWithContext* li)	{
	if (li)
		ThrowError (msg, li->linenu, li->text.c_str(), li->file.c_str());
	else 
		ThrowError (msg, 0, NULL, NULL);
}

static
void ThrowPartialError (const string & msg) {
	throw parser_partial_error(msg);
}

static
void CheckNumTokens (const vector<string>& p, 
	unsigned int min, unsigned int max, 
	unsigned int linenum= 0, const string* line= NULL,
	const string* file= NULL)		
{
	assert (p.size()> 0);
	assert (max== 0 || min<= max);

	string error_msg;

	if (min> 0 && p.size()< min)
		error_msg= "Not enough parameters for command " + p[0];
	else if (max> 0 && p.size()> max)
		error_msg= "Too many parameters for command " + p[0];
	else 
		return;

	ThrowError (error_msg, linenum, (line? line->c_str() : NULL), (file? file->c_str() : NULL));
}

static
void ExtractTokens (const string& s, vector<string>& result) {		
	escaped_list_separator<char> sep("\\", "\t ", "\"'");
	tokenizer<escaped_list_separator<char> > tok(s, sep);
	BOOST_FOREACH(string t, tok) {
		if (t.size()) result.push_back (t);
	}
}
static
void ArgumentSubstitution (	vector<string>& tokens, 
						const map<string,string>& dict, 
						vector<LineWithContext>::const_iterator& it_err)
{
	// Substitute tokens using the dictionary. The first token is not taken
	// into account.

	if (tokens.size()< 2) return;	// Nothing to do
	
	// Make iterator point to the second token
	vector<string>::iterator it= tokens.begin();
	++it;

	while (it!= tokens.end()) {
		//
		// Argument substitution
		//
		bool changed= false;
		size_t arg_index = 0, arg_size= 0;
		while (true) {
			// Locate substring
			arg_index = it->find("$", arg_index);
			if (arg_index== string::npos) break;
			arg_size = it->find("$", arg_index + 1);	// Here returns the index, not size...
			if (arg_size == string::npos)
				ThrowError("Argument terminator $ not found", &(*it_err));
			arg_size= arg_size - arg_index + 1;			// ... here computes the actual size
			assert (arg_size>= 2);

			// Extract arg name
			string arg_name= it->substr(arg_index, arg_size);

			// Dictionary look up
			map<string, string>::const_iterator d_it;
			d_it= dict.find (arg_name);
			if (d_it== dict.end()) {
				string error_msg("Argument ");
				error_msg+= arg_name;
				error_msg+= " not found";
				ThrowError(error_msg, &(*it_err));
			}
			
			// Make the replacement
			it->replace(arg_index, arg_size, d_it->second);
			changed= true;

			// Make index point after substitution result
			arg_index+= d_it->second.size();
		}

		//
		// Fix tokenization
		//
		// As a result of substitution, the number of tokens could have changed
		//
		if (changed) {
			trim(*it);
			vector<string> newTokens;
			try{
				ExtractTokens (*it, newTokens);
			}
			catch(std::exception& e) {
				ThrowError (e.what(), &(*it_err));
			}
			if (newTokens.size()== 0)
				// Remove token
				it= tokens.erase (it);
			else {
				// One or more tokens
				*it= newTokens[0];
				++it;
				
				for (unsigned int i= 1; i< newTokens.size(); ++i) {
					it= tokens.insert(it, newTokens[i]);
					assert (it!= tokens.end());
					++it;
				}
			}
		}
		else 
			++it;
	}
}

static
int SearchBeginTranslatable(const string& src, unsigned int pos)
{
	for (; pos< src.size(); ++pos) {
		if (src[pos]== '\\') {
			// Begin scape sequence ignore this character and the next one
			pos++;
		}
		else if (src[pos]== '_' && pos< src.size()-1 && src[pos+1]== '"') return pos;
	}

	return -1;
}

static
int SearchEndTranslatable(const string& src, unsigned int pos)
{
	for (; pos< src.size(); ++pos) {
		if (src[pos]== '\\') {
			// Begin scape sequence ignore this character and the next one
			pos++;
		}
		else if (src[pos]== '"') return pos;
	}

	return -1;
}

// check that the new translation is sane which 
// which basically means that does not contain unscaped quotes
// TODO: check that it is a valid utf-8 string
// TODO: manage properly strings containing scaped quotes
static
bool TranslationIsSane(const char* src)
{
	if (!src) return false;

	for (unsigned int i= 0; src[i]; ++i) {
		if (src[i]== '\\') {
			// Begin scape sequence ignore this character and the next one
			++i;
			if (src[i]== 0) return false;
		}
		else if (src[i]== '"') return false;
	}
	
	return true;
}

// Translation of translatable strings. First token is never translated.
static
void PerformStringTranslation (string& src)
{
	unsigned int pos= 0;

	for (;;) {
		int pos_ini= SearchBeginTranslatable(src, pos);
		if (pos_ini== -1) break;

		// Translatable sequence start found
		int pos_end= SearchEndTranslatable(src, pos_ini + 2);
		if (pos_end== -1) break;

		assert(pos_end> pos_ini+1);

		// Translatable sequence end found
		// pos_ini points to '_' and pos_end to the closing quote
		int text_pos_ini= pos_ini + 2;
		int text_size= pos_end - text_pos_ini;
		
		string text= src.substr(text_pos_ini, text_size);
		const char* text_ptr= text.c_str();

		const char* trans_ptr= dgettext(GETTEXT_DOMAIN_SCRIPTS, text_ptr);
		if (text_ptr!= trans_ptr) {
			// Text has been translated,
			if (!TranslationIsSane(trans_ptr)) trans_ptr= text_ptr;
		}

		// Substitute original content with translated version
		string trans("\"");
		trans+= trans_ptr;
		trans+= "\"";

		src.replace(pos_ini, pos_end - pos_ini + 1, trans);

		pos= pos_ini + trans.size();
	}
}

static
IComponent* FindComponentByName (IComponent& c, const char * name, const LineWithContext* li) {
	IComponent* retval= ComponentHelper::FindComponentByName (c, name, 1);
	if (!retval) 
		ThrowError (string("Component ") + name + " not found", li);
	return retval;
}

static
IInputPin* FindInputPin(IComponent& c, const char * name, const LineWithContext* li) {
	IInputPin* ip= IComponent::FindInputPin(c, name);
	if (!ip) 
		ThrowError (string("Input pin ") + name + " not found for component " + c.GetName(), li);
	return ip;
}

static
IOutputPin* FindOutputPin(IComponent& c, const char * name, const LineWithContext* li) {
	IOutputPin* op= IComponent::FindOutputPin(c, name);
	if (!op) 
		ThrowError (string("Output pin ") + name + " not found for component " + c.GetName(), li);
	return op;
}

static
string GetDirectory(const string& fullPath) {
	// Get directory part
	string dir;
	size_t ipathend= fullPath.find_last_of("\\/");
	if (ipathend!= string::npos)
		// Found
		dir= fullPath.substr(0, ipathend);

	return dir;
}

/**
	Class GUIContainer

	Models a GUI container, which can be a window or a layout helper (sizer).
	Does not take ownship of the actual window or sizer.
*/

class GUIContainer
{
	enum { BORDER_SIZE= 2 };
public:
	GUIContainer(wxObject* obj) : m_theObj(obj) {
		// Can be constructed with NULL object in test mode
#ifndef NDEBUG
		if (obj) assert (IsWindow() || IsLayout());
#endif
	}

	bool IsWindow() const {
		assert (m_theObj);
		return m_theObj->IsKindOf(wxClassInfo::FindClass(_T("wxWindow")));
	}

	bool IsLayout() const {
		assert (m_theObj);
		return m_theObj->IsKindOf(wxClassInfo::FindClass(_T("wxSizer")));
	}

	bool IsBook() const {
		assert (m_theObj);
		return m_theObj->IsKindOf(wxClassInfo::FindClass(_T("wxNotebook")));
	}

	// Get the pointer to the window object associated with the
	// window or sizer
	wxWindow* GetEffectiveWindow() {
		assert (m_theObj);
		if (IsLayout()) {
			assert (!static_cast<wxSizer*>(m_theObj)->GetContainingWindow()->IsKindOf(wxClassInfo::FindClass(_T("wxStaticBox"))));
			return static_cast<wxSizer*>(m_theObj)->GetContainingWindow();
		}

		// Is window
		if (IsCollapsiblePane())
			return static_cast<wxGenericCollapsiblePane*>(m_theObj)->GetPane();
		else
			return static_cast<wxWindow*>(m_theObj);
	}

	void AddChild (GUIContainer& cld, const LineWithContext* li) {
		assert (m_theObj);
		assert (cld.IsWindow() || cld.IsLayout());

		if (IsWindow()) {
			wxWindow* theWin= GetEffectiveWindow();
			assert (theWin);

			if (cld.IsLayout()) {
				// Parent is a window and child is a sizer => set the window sizer
				if (IsBook())
					ThrowError ("Cannot set a layout to a book (a book page is needed)", li);
				theWin->SetSizer(static_cast<wxSizer*>(cld.m_theObj));				
			}
			else {
				// Parent is a window and child is window 
				
				// Check if is a book
				if (IsBook()) {
					wxNotebook* book= static_cast<wxNotebook*>(m_theObj);
					book->AddPage(static_cast<wxWindow*>(cld.m_theObj), static_cast<wxWindow*>(cld.m_theObj)->GetName());
				}
				else {
					// In other cases check is there is a sizer available and use it
					wxSizer* sizer= GetEffectiveWindow()->GetSizer();
					if (sizer)
						sizer->Add(static_cast<wxWindow*>(cld.m_theObj), 0, wxALIGN_LEFT|wxALL, BORDER_SIZE);
				}
				
				// make sure the parent-child relationship already exists
				assert (cld.GetEffectiveWindow()->GetParent()== GetEffectiveWindow());
			}
		}
		else {
			// Parent is a sizer
			if (cld.IsLayout()) {
				// Parent is a sizer and child is a sizer
				static_cast<wxSizer*>(m_theObj)->Add(static_cast<wxSizer*>(cld.m_theObj), 0, wxALIGN_LEFT|wxALL, BORDER_SIZE);
			}
			else {
				// Parent is a sizer and child is a window
				static_cast<wxSizer*>(m_theObj)->Add(static_cast<wxWindow*>(cld.m_theObj), 0, wxALIGN_LEFT|wxALL, BORDER_SIZE);
			}
		}
	}

private:

	bool IsCollapsiblePane() const {
		assert (m_theObj);
		return	m_theObj->IsKindOf(wxClassInfo::FindClass(_T("wxGenericCollapsiblePane")));
	}

	wxObject* m_theObj;
};

/**
	Component class used to return instances loaded from scripts
*/
class ScriptComponent : public CCompositeComponentAdapter {
public:
    ScriptComponent(const char * type, const char * name, int argc, const char * argv[])
    : CCompositeComponentAdapter(name, argc, argv)
	, m_type(type)
	{}

	virtual const char* GetTypeName() const {
		return m_type.c_str();
	}

	int RegisterInputPin (IInputPin& ipin) {
		return CCompositeComponentAdapter::RegisterInputPin(ipin);
	}

	int RegisterOutputPin (IOutputPin& opin) {
		return CCompositeComponentAdapter::RegisterOutputPin(opin);
	}

	void AddLayoutLine (const LineWithContext& l) {
		m_layoutLines.push_back(l);
	}

	virtual wxWindow* GetGUI(wxWindow * parent) {
		wxWindow* panel= NULL;
		try {
			panel= GetGUI(parent, false);
		}
		catch(std::exception& e) {
			getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_ERROR, e.what(), "");
			if (panel) panel->Destroy();
		}

		return panel;
	}

	wxWindow* GetGUI(wxWindow * parent, bool test) {
		if (m_layoutLines.size()== 0) return NULL;
		
		// We need a stack to parse nested sizers and containers
		std::stack<GUIContainer> containerStack;

		// Create panel
		if (!test) {
			widgets_base::ContainerPanel* panel= NULL;
			panel= new widgets_base::ContainerPanel();
			panel->Create(parent);
			panel->SetName (wxString(GetName(), wxConvUTF8));
			containerStack.push(GUIContainer(panel));
		}
		else
			containerStack.push(GUIContainer(NULL));

		// A set is used to ensure component are not place more than once
		std::set<string> usedComponents;

		// For each stored line
		vector<LineWithContext>::const_iterator it= m_layoutLines.begin();
		for (; it!= m_layoutLines.end(); ++it) {
			//
			// Parse line
			//			
			vector<string> t;
			try {
				ExtractTokens (it->text, t);
			}
			catch(std::exception& e) {
				ThrowError (e.what(), &(*it));
			}

			assert (containerStack.size());

			if (t[0]== "layout_begin") {
				CheckNumTokens (t, 2, 3, it->linenu, &it->text, &it->file);

				if (t[1]== "hbox") {
					wxSizer* sizer= NULL;
					if (!test) {
						if (t.size()== 3) {
							wxStaticBox* box= 
								new wxStaticBox(containerStack.top().GetEffectiveWindow(), 
									wxID_ANY, wxString(t[2].c_str(), wxConvUTF8));

								sizer= new wxStaticBoxSizer(box, wxHORIZONTAL);
						}
						else
							sizer= new wxBoxSizer(wxHORIZONTAL);
						GUIContainer child(sizer);
						containerStack.top().AddChild (child, &(*it));
					}
					GUIContainer child(sizer);
					containerStack.push(child);
				}
				else if (t[1]== "vbox") {
					wxSizer* sizer= NULL;
					if (!test) {
						if (t.size()== 3) {
							wxStaticBox* box= 
								new wxStaticBox(containerStack.top().GetEffectiveWindow(), 
									wxID_ANY, wxString(t[2].c_str(), wxConvUTF8));

								sizer= new wxStaticBoxSizer(box, wxVERTICAL);
						}
						else
							sizer= new wxBoxSizer(wxVERTICAL);
						GUIContainer child(sizer);
						containerStack.top().AddChild (child, &(*it));
					}
					GUIContainer child(sizer);
					containerStack.push(child);
				}
				else if (t[1]== "book") {
					CheckNumTokens (t, 2, 2, it->linenu, &it->text, &it->file);
					wxNotebook* notebook= NULL;
					if (!test) {
						notebook= new ContainerBook (containerStack.top().GetEffectiveWindow(), wxID_ANY);
						GUIContainer child(notebook);
						containerStack.top().AddChild (child, &(*it));
					}
					GUIContainer child(notebook);
					containerStack.push(child);
				}
				else if (t[1]== "book_page") {
					wxWindow* panel= NULL;
					if (!test) {
						if (!containerStack.top().IsBook())
							ThrowError ("Statement book_page without book", &(*it));
						panel= new widgets_base::ContainerPanel(containerStack.top().GetEffectiveWindow(), wxID_ANY);
						// Set sizer
						panel->SetSizer(new wxBoxSizer(wxVERTICAL));

						// Provides name?
						if (t.size()== 3)
							panel->SetName(wxString(t[2].c_str(), wxConvUTF8));

						GUIContainer child(panel);
						containerStack.top().AddChild (child, &(*it));
					}
					GUIContainer child(panel);
					containerStack.push(child);
				}
				else if (t[1]== "collapsible") {
					wxGenericCollapsiblePane* panel= NULL;

					if (!test) {
						//panel= new wxGenericCollapsiblePane(containerStack.top().GetEffectiveWindow(), wxID_ANY, _("Advanced"));
						panel= new widgets_base::ContainerCollapsible(containerStack.top().GetEffectiveWindow(), wxID_ANY, _("Advanced"));
						GUIContainer child(panel);
						containerStack.top().AddChild (child, &(*it));
						// Provides label?
						if (t.size()== 3)
							panel->SetLabel(wxString(t[2].c_str(), wxConvUTF8));

						// Set sizer
						panel->GetPane()->SetSizer(new wxBoxSizer(wxVERTICAL));

					}
					GUIContainer child(panel);
					containerStack.push(child);
				}
				else if (t[1]== "component") {
					CheckNumTokens (t, 2, 3, it->linenu, &it->text, &it->file);

					IComponent* compo= FindComponentByName (*this, t[2].c_str(), &(*it));
					if (!compo) {
						assert (test);
						string error_msg("Component ");
						error_msg+= t[2] + " not found";
						ThrowError (error_msg, &(*it));
					}

					if (strcmp(compo->GetTypeName(), "widget_collapsible")!= 0) {
						// Only widget_collapsible is supported
						assert (test);
						ThrowError ("Only widget_collapsible components can be used here", &(*it));
					}

					wxGenericCollapsiblePane* panel= NULL;

					if (!test) {
						//panel= new widgets_base::ContainerCollapsible(containerStack.top().GetEffectiveWindow(), wxID_ANY, _("Advanced"));
						wxWindow* w= compo->GetGUI(containerStack.top().GetEffectiveWindow());
						assert (w);
						assert (w->IsKindOf(wxClassInfo::FindClass(_T("wxGenericCollapsiblePane"))));
						panel= static_cast<wxGenericCollapsiblePane*>(w);
						GUIContainer child(panel);
						containerStack.top().AddChild (child, &(*it));
						// Set sizer
						panel->GetPane()->SetSizer(new wxBoxSizer(wxVERTICAL));
					}
					GUIContainer child(panel);
					containerStack.push(child);
				}
				else
					ThrowError ("Unknown layout type", &(*it));
			}
			else if (t[0]== "layout_end") {
				CheckNumTokens (t, 1, 1, it->linenu, &it->text, &it->file);

				if (containerStack.size()== 1)
					ThrowError ("Unmatched layout_end", &(*it));

				containerStack.pop();
			}
			else if (t[0]== "component") {
				CheckNumTokens (t, 2, 2, it->linenu, &it->text, &it->file);

				// Already placed?
				if (usedComponents.find(t[1])!= usedComponents.end())
					ThrowError (string("Component ") + t[1] + " already placed on GUI", &(*it));

				// Search if exists
				IComponent* compo= FindComponentByName (*this, t[1].c_str(), &(*it));
				assert (compo);

				// Set as placed
				usedComponents.insert(t[1]);

				// Add
				if (!test) {
					wxWindow* newPan= compo->GetGUI(containerStack.top().GetEffectiveWindow());
					if (!newPan) {
						string msg("Component ");
						msg+= t[1] + " has no GUI while building GUI.";
						getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_WARNING, msg.c_str(), GetName());
					}
					else {
						assert (containerStack.size());
						GUIContainer child(newPan);
						containerStack.top().AddChild(child, &(*it));
					}
				}
			}
			else 
				ThrowError ("Syntax error while parsing GUI layout", &(*it));
		}

		assert (containerStack.size());
		if (containerStack.size()> 1) {
			ThrowError ("Missing layout_end after", m_layoutLines.rbegin()->linenu, 
				NULL, m_layoutLines.rbegin()->file.c_str());
		}


		if (!test) {
			assert (containerStack.top().GetEffectiveWindow()->IsKindOf(wxClassInfo::FindClass(_T("wxWindow"))));
			return static_cast<wxWindow*>(containerStack.top().GetEffectiveWindow());
		}
		else 
			return NULL;
	}

private:
	string m_type;
	vector< LineWithContext > m_layoutLines;
};

/**
	Quick and dirty class to parse scripts
*/
class ParsingContext
{
public:
	// Parses an stream and returns a constructed component or
	// throwns an exception if an error is detected
	SmartPtr<IComponent> SimpleParse (std::istream& is, const string& file)
	{
		// Pre-processing stage
		unsigned int linenum= 0;
		PreProcess (is, true, linenum, file);
		CheckComponentTypeName (linenum);

		// Component build stage		
		return BuildComponent(NULL, 0, NULL);
	}

private:
		
	// Checks that the type of the component is not void and not already in use
	void CheckComponentTypeName (int linenum) { //, const string& file) {
		// Check child component type name
		if (m_type.size()== 0)
			ThrowError ("Parse error. Component type not found after", linenum, NULL, m_file.c_str());			

		// Check registered components
		SmartPtr<IIterator<IComponentFactory*> > it = getSpCoreRuntime()->QueryComponents();
		for (; !it->IsDone(); it->Next()){
			if (m_type== it->CurrentItem()->GetName()) {
				string error_msg("Parse error. Component type ");
				error_msg+= m_type + " clashes with one registered component type";
				ThrowError (error_msg, linenum, NULL, m_file.c_str());
			}
		}		
	}

	// Peforms script preprocessing and builds a tree of parsing contexts
	void PreProcess (istream& is, bool is_root, unsigned int& linenum, const string& file) {
		string line;

		m_file= file;

		while (!is.eof()) {
			getline (is, line);
			trim (line);
			++linenum;

			// If blank or comment ignore line
			if (line.size()== 0) continue;
			if (line[0]== '#') continue;

			PerformStringTranslation (line);
			vector<string> t;
			try {
				ExtractTokens (line, t);
			}
			catch(std::exception& e) {
				ThrowError (e.what(), linenum, line.c_str(), m_file.c_str());
			}
			
			if (t[0]== "import") {
				// Import script
				CheckNumTokens (t, 2, 2, linenum, &line, &m_file);

				// First try to access file directly
				string fname;
				if (access(t[1].c_str(), R_OK)== 0) fname= t[1];

				// If not worked try appending current script path
				if (!fname.size()) {
					fname= GetDirectory(m_file);
					fname+= "/";
					fname+= t[1];
				}

				if (!fname.size()) {
					string err_msg("Cannot open file ");
					err_msg+= t[1];
					ThrowError (err_msg, linenum, line.c_str(), m_file.c_str());
				}

				// File exists and is readable
				ifstream ifs ( fname.c_str(), ifstream::in );
				if (!ifs.is_open() || !ifs.good()) {
					// Should not happen, but just in case...
					string err_msg("Cannot open file ");
					err_msg+= t[1];
					ThrowError (err_msg, linenum, line.c_str(), m_file.c_str());
				}

				shared_ptr<ParsingContext> child= shared_ptr<ParsingContext>(new ParsingContext);

				unsigned int child_linenum= 0;
				child->PreProcess (ifs, true, child_linenum, fname);

				// Try to add to the subcomponents map
				pair<map<string,shared_ptr<ParsingContext> >::iterator,bool> ret;
				ret= m_subcomponents.insert (pair<string,shared_ptr<ParsingContext> >(child->m_type, child));
				if (ret.second== false) {
					// Component already found
					string error_msg("Parse error. Component ");
					error_msg+= child->m_type + " redefined before line ";
					ThrowError (error_msg, linenum, NULL, m_file.c_str());
				}
			}
			else if (t[0]== "args") {
				CheckNumTokens (t, 2, 0, linenum, &line, &m_file);
				
				for (unsigned int i= 1; i< t.size(); i++) {
					// All arguments must have a leading dash
					assert (t[i].size());
					if (t[i][0]!= '-')
						ThrowError ("Missing leading dash '-' in argument declaration", linenum, line.c_str(), m_file.c_str());
					
					// Already declared?
					if (m_expectedArguments.find(t[i])!= m_expectedArguments.end())
						ThrowError (string("Argument ") + t[i] + " already defined", linenum, line.c_str(), m_file.c_str());
					
					// Add to expected arguments set
					m_expectedArguments.insert(t[i]);
				}
			}
			else if (t[0]== "subcomponent") {
				// Subcomponent found
				CheckNumTokens (t, 1, 1, linenum, &line, &m_file);
		
				shared_ptr<ParsingContext> child= shared_ptr<ParsingContext>(new ParsingContext);

				child->PreProcess (is, false, linenum, m_file);

				// Try to add to the subcomponents map
				pair<map<string,shared_ptr<ParsingContext> >::iterator,bool> ret;
				ret= m_subcomponents.insert (pair<string,shared_ptr<ParsingContext> >(child->m_type, child));
				if (ret.second== false) {
					// Component already found
					string error_msg("Parse error. Component ");
					error_msg+= child->m_type + " redefined before line ";
					error_msg+= boost::lexical_cast<std::string>(linenum);
					throw parser_error(error_msg);
				}
			}
			else if (t[0]== "subcomponent_end") {
				
				CheckNumTokens (t, 1, 1, linenum, &line, &m_file);

				if (is_root) {
					string error_msg("Unexpected subcomponent_end keyword");
					ThrowError (error_msg, linenum, NULL, m_file.c_str());
				}

				CheckComponentTypeName (linenum); //, m_file);

				// Ok, finished at context, just return
				return;
			}
			else if (t[0]== "type") {
				CheckNumTokens (t, 2, 2, linenum, &line, &m_file);
				m_type= t[1];			
				CheckComponentTypeName (linenum); //, m_file);				
			}
			else if (t[0]== "name") {
				CheckNumTokens (t, 2, 2, linenum, &line, &m_file);
				m_name= t[1];
			}
			else {
				// Other lines are simply translated & stored for further processing
				//PerformStringTranslation (line);
				m_lines.push_back(LineWithContext(line, linenum, m_file));
			}
		}

		if (!is_root) {
			string error_msg("Missing subcomponent_end keyword");

			ThrowError (error_msg, linenum, NULL, m_file.c_str());
		}
	}

	
	//
	// Buils a component given this parsing context
	//
	// Param with_name tells to rename the component with the given name,
	// otherwise keyword "name" is expected to appear at the very beggining of the script
	//
	SmartPtr<IComponent> BuildComponent(const char * with_name, int argc, const char* argv[]) {		
		// If no lines, flag error
		if (m_lines.size()== 0)
			ThrowError ("No elements found for", 0, with_name, m_file.c_str());
		
		SmartPtr<ScriptComponent> result;
		
		// 
		// Process incoming arguments
		//
		// Map to allow to substitute arguments into its values
		map<string, string> argumentsDict;

		// Insert $SP_DATA_DIR$ value
		argumentsDict.insert(pair<string, string>("$SP_DATA_DIR$", getSpCoreRuntime()->GetPaths().GetDataDir()));

		if (argc) {
			assert (argv);
			for (int i= 0; i< argc; ++i) {
				// Argument name
				const char* aname= argv[i];
				if (m_expectedArguments.find(aname)== m_expectedArguments.end()) {
					string error_msg("Unexpected argument ");
					error_msg+= string(argv[i]);
					ThrowPartialError(error_msg);
				}

				// Decorate name
				string aname_decor= string("$") + aname + string("$");

				// Is already in map?
				if (argumentsDict.find(aname_decor)!= argumentsDict.end()) {
					string error_msg("Argument ");
					error_msg+= argv[i];
					error_msg+= " set more than once";
					ThrowPartialError(error_msg);
				}

				// Argument values				
				string value;
				if (i+1< argc && argv[i+1][0]!= '-') {
					value= argv[i+1];
					++i;
				}

				// Add to map
				pair<map<string, string>::iterator,bool> retval;
				retval= argumentsDict.insert( pair<string, string> (aname_decor, value));
				assert (retval.second);
			}
		}

		// Check whether the number of expected arguments match
		if (argumentsDict.size() - 1!= m_expectedArguments.size()) {
			string error_msg("Not enough arguments");
			ThrowPartialError(error_msg);
		}

		// Create the component on which children will be added
		if (with_name) result= CreateScriptComponent (m_type.c_str(), with_name);
		else {
			if (m_name.size()== 0) ThrowError ("Component name not found.", NULL);
			result= CreateScriptComponent (m_type.c_str(), m_name.c_str());
		}
		
		// Flag for GUI layout script lines
		bool parsingLayout= false;

		// For each stored line
		vector<LineWithContext>::const_iterator it= m_lines.begin();
		for (; it!= m_lines.end(); ++it) {
			//
			// Parse line
			//
			if (parsingLayout) {
				if (it->text== "end_gui_layout") parsingLayout= false;
				else result->AddLayoutLine(*it);
				continue;
			}

			// Extract tokens
			vector<string> t;
			try {
				ExtractTokens (it->text, t);
			}
			catch(std::exception& e) {
				ThrowError (e.what(), &(*it));
			}

			ArgumentSubstitution (t, argumentsDict, it);
			
			if (t[0]== "create") {
				CheckNumTokens (t, 3, 0, it->linenu, &it->text, &it->file);
				
				// Collect args
				int argc= 0;
				const char** argv= NULL;
				if (t.size()> 3) {
					argv= new const char*[t.size()-3];
					for (unsigned int i= 3; i< t.size(); ++i) {
						// Store argv argument
						argv[argc++]= t[i].c_str();
					}
				}

				// Create component
				// TODO: check whether the component exists or not and provide better error message
				SmartPtr<IComponent> compo;
				try {
					// Check subcomponents
					map<string,shared_ptr<ParsingContext> >::iterator itm= m_subcomponents.find (t[1]);
					if (itm!= m_subcomponents.end()) compo= itm->second->BuildComponent(t[2].c_str(), argc, argv);
					else {
						compo= getSpCoreRuntime()->CreateComponent(t[1].c_str(), t[2].c_str(), argc, argv);				
						if (compo.get()== NULL) {
							ThrowError (string("Component " + t[1] + " not found or creation failed (see console)"), &(*it));
						}
					}
				}
				catch (parser_partial_error& e) {
					delete[] argv;
					ThrowError(e.what(), &(*it));
				}
				catch(...) {
					delete[] argv;
					throw;
				}		

				delete[] argv;
				argv= NULL;

				// Attach to parent				
				if (result->AddChild(compo)!= 0) 
					ThrowError (string("Another component with name " + t[2] + " already found"), &(*it));
			}
			else if (t[0]== "connect") {
				CheckNumTokens (t, 5, 5, it->linenu, &it->text, &it->file);
								
				// Find components
				IComponent* src_component= FindComponentByName (*result, t[1].c_str(), &(*it));
				IComponent* dst_component= FindComponentByName (*result, t[3].c_str(), &(*it));
				
				// TODO: this is a security measure to avoid stack overflows in some cases
				// but actually core must provide a built-in stack overflow protection
				if (src_component== dst_component) 
					ThrowError ("Source and destination components are the same", &(*it));
				
				// Find pins
				IOutputPin* out_pin= FindOutputPin(*src_component, t[2].c_str(), &(*it));				
				IInputPin* in_pin= FindInputPin(*dst_component, t[4].c_str(), &(*it));
				
				// Connect pins
				if (out_pin->Connect (*in_pin)!= 0) 
					ThrowError ("Type mismatch connecting pins", &(*it));
			}
			else if (t[0]== "export_ipin") {
				CheckNumTokens (t, 3, 5, it->linenu, &it->text, &it->file);
				
				IComponent* compo= FindComponentByName (*result, t[1].c_str(), &(*it));
				IInputPin* ipin= FindInputPin(*compo, t[2].c_str(), &(*it));

				// Check to see if already exported
				{
					SmartPtr<IIterator<IInputPin*> > itip= result->GetInputPins();
					for (; !itip->IsDone(); itip->Next())
						if (ipin== itip->CurrentItem())
							ThrowError ("Input pin already exported", &(*it));
				}

				// Rename?
				if (t.size()>= 4) {
					const string& new_name= t[3];
					// Check if the new name will clash with another pin 
					// of the original component
					if (new_name!= ipin->GetName()) {
						// If the new name does not coincides with the old one
						SmartPtr<IIterator<IInputPin*> > itip= compo->GetInputPins();
						for (; !itip->IsDone(); itip->Next())
							if (new_name== itip->CurrentItem()->GetName())
								ThrowError (
									"Another pin with the same name exists.\n"
									"Cannot rename pin", &(*it)
								);
						ipin->Rename(new_name.c_str());
					}
				}

				// Change type?
				if (t.size()== 5)	
					if (ipin->ChangeType (t[4].c_str())!= 0) 
						ThrowError ("Cannot change type of the pin", &(*it));

				// Try to register
				if (result->RegisterInputPin(*ipin)!= 0)
					ThrowError ("Input pin name clashes with an existing one", &(*it));
			}
			else if (t[0]== "export_opin") {
				CheckNumTokens (t, 3, 5, it->linenu, &it->text, &it->file);
				
				IComponent* compo= FindComponentByName (*result, t[1].c_str(), &(*it));
				IOutputPin* opin= FindOutputPin(*compo, t[2].c_str(), &(*it));

				// Check to see if already exported
				{
					SmartPtr<IIterator<IOutputPin*> > itop= result->GetOutputPins();
					for (; !itop->IsDone(); itop->Next())
						if (opin== itop->CurrentItem())
							ThrowError ("Output pin already exported", &(*it));
				}
				
				// Rename?
				if (t.size()>= 4) {
					const string& new_name= t[3];
					// Check if the new name will clash with another pin 
					// of the original component
					if (new_name!= opin->GetName()) {
						// If the new name does not coincides with the old one
						SmartPtr<IIterator<IOutputPin*> > itop= compo->GetOutputPins();
						for (; !itop->IsDone(); itop->Next())
							if (new_name== itop->CurrentItem()->GetName())
								ThrowError (
									"Another pin with the same name exists.\n"
									"Cannot rename pin", &(*it)
								);

						opin->Rename(new_name.c_str());
					}
				}

				// Change type?
				if (t.size()== 5)	
					if (opin->ChangeType (t[4].c_str())!= 0) 
						ThrowError ("Cannot change type of the pin", &(*it));

				// Try to register
				if (result->RegisterOutputPin(*opin)!= 0)
					ThrowError ("Output pin name clashes with an existing one", &(*it));
			}
			else if (t[0]== "begin_gui_layout") {
				if (parsingLayout)
					ThrowError ("Unexpected begin_gui_layout", &(*it));
				parsingLayout= true;				
			}
			else 
				// Syntax error
				ThrowError ("Syntax error", &(*it));
		}

		assert (result.get());
		
		// Check GUI layout
		result->GetGUI(NULL, true);
		
		return result;
	}	

	//
	// Convenience functions
	//

	// Create instance of ScriptComponent class taking care of errors
	static 
	SmartPtr<ScriptComponent> CreateScriptComponent (const char * type, const char * name) {
		SmartPtr<ScriptComponent> result= SmartPtr<ScriptComponent>(new ScriptComponent(type, name, 0, NULL), false);
		assert (result.get());
		if (result.get()== NULL) 
			throw parser_error("Fatal error cannot create ScriptComponent");
		return result;
	}

private:
	string m_type;	// Name of the type of the component
	string m_name;	// Name given to the component
	string m_file;	// Associated file name (for error reporting)

	// Expected arguments for this component
	set<string> m_expectedArguments;

	// Stores script lines with additional context information
	vector< LineWithContext > m_lines;
	// Subcomponents (stored in a vector for simplif
	map<string,shared_ptr<ParsingContext> > m_subcomponents;
};

SmartPtr<IComponent> ComponentHelper::SimpleParser (std::istream& is, const string& file)
{
	ParsingContext pc;

	return pc.SimpleParse (is, file);
}


SmartPtr<spcore::IComponent> ComponentHelper::SimpleParserFile (const char* file)
{
	ifstream ifs ( file , ifstream::in );

	if (!ifs.is_open() || !ifs.good()) throw std::runtime_error("Error opening file");
		
	return SimpleParser (ifs, string(file));
}


#ifdef WIN32
// It seems that MSVC compilers get fooled in the following function
// due to an identical loop guard and prints an unreachable code warning
#pragma warning (disable:4702)
#endif

IComponent* ComponentHelper::FindComponentByName (spcore::IComponent& component, const char* name, int deep)
{
	assert (deep>= 0);

	SmartPtr< IIterator<IComponent*> > it= component.QueryComponents();
	if (it.get()== NULL) return NULL;

	for (; !it->IsDone(); it->Next()) {
		if (strcmp(name, it->CurrentItem()->GetName())== 0)
			return it->CurrentItem();	// Found
	}


	if (deep!= 1) {
		for (it->First(); !it->IsDone(); it->Next()) {
			if (deep> 0) --deep;
			return FindComponentByName (*it->CurrentItem(), name, deep);
		}
	}

	return NULL;
}

};
