/*                       
	This file is part of the GVars3 Library.

	Copyright (C) 2005 The Authors

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library 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
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 
    51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "gvars3/GUI.h"
#include "gvars3/GStringUtil.h"



#include <cctype>
#include <sstream>
#include <fstream>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <algorithm>


using namespace std;
namespace GVars3
{
	//GUI GUI;

bool setvar(string s)
{
	//Execution failed. Maybe its an assignment.
        string::size_type n;
	n=s.find("=");

	if(n != string::npos)
	{    
		string var = s.substr(0, n);
		string val = s.substr(n+1);

		//Strip whitespace from around var;
		string::size_type s=0, e = var.length()-1; 
		for(; isspace(var[s]) && s < var.length(); s++);
		for(; isspace(var[e]) && e >=0; e--);

		if(e >= s)
		{
			var = var.substr(s, e-s+1);
			
			//Strip whitespace from around val;			
			s = 0, e = val.length() - 1;
			for(; isspace(val[s]) && s < val.length(); s++);
			for(; isspace(val[e]) && e >=0; e--);

			val = val.substr(s, e-s+1);

			GV3::set_var(var, val);
			return true;
		}
	}

	return false;
}

char* GV3ReadlineCommandGenerator(const char *szText, int nState)
{
	static vector<string> tags;
	static vector<string>::iterator tag_i;
	
	if(!nState)
	{
		tags = GV3::tag_list();
		tag_i = tags.begin();
		
	};

	while(tag_i != tags.end())
	{
		int text_len = strlen(szText);
		bool bEqualsAtEnd = false;
		if(text_len>0)
		  if(szText[text_len-1]=='=')
		    bEqualsAtEnd = true;
		string sCurrent = *tag_i;
		string sComparison = *tag_i;
		if(bEqualsAtEnd)
		  sComparison = sComparison + "=";

		tag_i++;

		if(strncmp(szText, sComparison.c_str(), text_len) == 0)
		  {
			//If it's a prefect match, complete with the value
		        string sCompleted;
			if(text_len == sComparison.size())
			  sCompleted = szText + ((bEqualsAtEnd?"":"=") + GV3::get_var(sCurrent));
			else
			  sCompleted = sCurrent;
			
			char* r;
			
			if((r= (char*)malloc(sCompleted.size() + 1)) == NULL)
				return 0;

			strcpy(r, sCompleted.c_str());
			return r;
		}
	}

	return 0;
}




GUI *GUI::mpReadlineCompleterGUI=NULL;


void GUI::UnRegisterCommand(string sCommandName)
{
	mmCallBackMap.erase(sCommandName);
};

void GUI::UnRegisterAllCommands(void* thisptr)
{
  for(map<string, CallbackVector>::iterator i=mmCallBackMap.begin(); i!=mmCallBackMap.end(); i++)
    UnRegisterCommand(i->first, thisptr);
};

void GUI::UnRegisterCommand(string sCommandName, void* thisptr)
{
	CallbackVector &cbv = mmCallBackMap[sCommandName];
	for(int i = cbv.size() - 1; i>=0; i--)
	      if(cbv[i].thisptr == thisptr)
		    cbv.erase(cbv.begin() + i);
};

void GUI::RegisterCommand(string sCommandName, GUICallbackProc callback, void* thisptr)
{
	if(builtins.count(sCommandName))
	{
		cerr << "!!GUI::RegisterCommand: Tried to register reserved keyword " << sCommandName << "." << endl;
		return;
	}

	CallbackInfoStruct s;
	s.cbp = callback;
	s.thisptr = thisptr;

	bool bAlreadyThere = false;
	CallbackVector *cbv = &mmCallBackMap[sCommandName];


	for(CallbackVector::iterator i = cbv->begin(); i<cbv->end();i++)
		if( (i->cbp ==s.cbp) && (i->thisptr == s.thisptr))
			bAlreadyThere=true;

	if(!bAlreadyThere)
			cbv->push_back(s);
};


// Returns true if something was called;
// otherwise returns false
bool GUI::CallCallbacks(string sCommand, string sParams)
{
	if(mmCallBackMap.count(sCommand)==0)
		return false;

	//Make a copy of this callback vector, since the command might call Unregister.
	CallbackVector cbv = mmCallBackMap[sCommand];
	if(cbv.size() == 0)
	        return false;

	for(CallbackVector::iterator i = cbv.begin(); i<cbv.end();i++)
		i->cbp(i->thisptr, sCommand, sParams);

	return true;
};


void GUI::ParseStream(istream& is)
{
	string buffer;
	while(getline(is, buffer))
		ParseLine(buffer);
}

void GUI::LoadFile(string sFileName)
{
	ifstream ifs;

	vector<string> v = ChopAndUnquoteString(sFileName);

	if(v.size()<1) 
		return;
		
	ifs.open(v[0].c_str());

	if(!ifs.good())
	{
		cerr << "! GUI::Loadfile: Failed to load script file \"" << sFileName << "\"."<< endl;
		return;
	}

	ParseStream(ifs);
	ifs.close();
}





string::size_type FindCloseBrace(const string& s, string::size_type start, char op, char cl)
{
        string::size_type open=1;
	string::size_type i;

	for(i=start+1; i < s.size() ; i++)
	{
		if(s[i] == op) 
			open++;
		else if(s[i] == cl) 
			open--;

		if(open == 0)
			break;
	}
	
	if(i == s.size())
		i = s.npos;
	return i;
}


void GUI::ParseLine(string s, bool bSilentFailure )
{
	s = UncommentString(s);	
	if(s == "")
		return;





	  // New for 2004! brace expansion! any line with {gvarname} in it 
	  // will have the brace bit replaced with the contents of that gvar,
	  // one line per word in the gvar value...
	  // e.g. 
	  // > TrackerList = Tracker FireTracker
	  // > {TrackerList}.ReloadModel 
	  // would be equivalent to writing 
	  // > Tracker.ReloadModel 
	  // > FireTracker.ReloadModel
	  // Can use double braces so that brace expansion only occurs later;
	  // e.g. GUI_Motif.AddPushButton DoStuffToTrackerList {{TrackerList}}.do_stuff
	  // will create a single button which then exectues {TrackerList}.do_stuff
	  // (Without double braces, the it would try to make two buttons!)

  {   // Brace expansion wrapper follows:
    string::size_type nOpenBracePos = s.find("{");
    //int nCloseBracePos = s.rfind("}");
    string::size_type nCloseBracePos = FindCloseBrace(s, nOpenBracePos, '{', '}');
    if( (nOpenBracePos  !=s.npos) && 
	(nCloseBracePos !=s.npos) &&
	(nCloseBracePos > nOpenBracePos))
      {   // Brace Pair found!!

	string sBegin = s.substr(0,nOpenBracePos);
	string sVarName = s.substr(nOpenBracePos+1,nCloseBracePos-nOpenBracePos-1);
	string sEnd = s.substr(nCloseBracePos+1);
	
	string::size_type nLength = sVarName.size();   // Check if it's a double brace: {{foo}} in which case remove one pair, but don't expand.
	bool bIsDoubleQuoted = false;
	
	if(nLength>2)
	  if((sVarName[0]=='{') && (sVarName[nLength-1]=='}'))
	    bIsDoubleQuoted = true;
	
	if(!bIsDoubleQuoted)
	  {
	    //vector<string> vsExpandedList = ChopAndUnquoteString(mpGV2->StringValue(sVarName, true));
	    vector<string> vsExpandedList = ChopAndUnquoteString(GV3::get_var(sVarName));

		//ER: look at the end of the string for '$'s to also perform the substitution
		vector<string> chopped;
		string end;
		string::size_type dollarpos=-1, lastpos=0;
		while((dollarpos = sEnd.find("$", dollarpos+1)) != s.npos)
		{
			chopped.push_back(sEnd.substr(lastpos, dollarpos - lastpos));
			lastpos = dollarpos+1;
		}
		end = sEnd.substr(lastpos);

	    for(vector<string>::iterator i = vsExpandedList.begin(); i!=vsExpandedList.end();i++)
		{
			string line = sBegin + *i;
			for(vector<string>::iterator s=chopped.begin(); s != chopped.end(); s++)
				line += *s + *i;
			line += end;

	      ParseLine(line, bSilentFailure);
		}
	    return;
	  };
	
	// if it was double quoted, just parse it as normal, but replace s with the new, single-braced thingy.
	s = sBegin + sVarName + sEnd;
      }
  }
  
  // Newer for 2004: Round brace expansion 
  // Expands gvar in round braces to the value
  // e.g.  A = 2
  //       B = (A)
  // assigns 2 to B
  // And as before, use double brace to protect.

	{   // Round expansion wrapper follows:

	        string::size_type nOpenBracePos = s.find("(");
		//int nCloseBracePos = s.rfind(")");
		string::size_type nCloseBracePos = FindCloseBrace(s, nOpenBracePos, '(', ')');

//		cerr << "((( " << nOpenBracePos << "  " << nCloseBracePos << endl;
		

		if((nOpenBracePos  !=s.npos) && (nCloseBracePos !=s.npos) && (nCloseBracePos > nOpenBracePos))
		{   // Brace Pair found!!
//cerr << "Found (\n";
			//cout << "Found brace pair. " << endl;
			string sBegin = s.substr(0,nOpenBracePos);
			string sVarName = s.substr(nOpenBracePos+1,nCloseBracePos-nOpenBracePos-1);
			string sEnd = s.substr(nCloseBracePos+1);

//cerr << "varname = --" << sVarName << "--\n";

			string::size_type nLength = sVarName.size();   // Check if it's a double brace: {{foo}} in which case remove one pair, but don't expand.
			bool bIsDoubleQuoted = false;

			if(nLength>2)
				if((sVarName[0]=='(') && (sVarName[nLength-1]==')'))
				{
					s = sBegin + sVarName + sEnd;  // Just remove the first set of braces.
					bIsDoubleQuoted = true;
				};

			if(!bIsDoubleQuoted)
			{
//cerr << "varname = --" << sVarName << "--\n";
//cerr << "***************" << GV3::get_var(sVarName) << endl;
				//string sExpanded = mpGV2->StringValue(sVarName, true);
				string sExpanded = GV3::get_var(sVarName);
				s = sBegin + sExpanded + sEnd;
				//cout << "DEBUG : xx" << s << "xx" << endl;
			};
		}
	}



	// Old ParseLine code follows, here no braces can be left (unless in arg.)
	istringstream ist(s);
		
	string sCommand;
	string sParams;
	
	//Get the first token (the command name)
	ist >> sCommand;
	if(sCommand == "")
		return;

	//Get everything else (the arguments)...
	
	//Remove any whitespace
	ist >> ws;
	getline(ist, sParams);
	
	//Attempt to execute command
	if (CallCallbacks(sCommand, sParams))
		return;
	
	if(setvar(s))
		return;

	if(!bSilentFailure) 
		cerr << "? GUI::ParseLine: Unknown command \"" << sCommand << "\" or invalid assignment." << endl;
}



////////////////////////////////////////////////////////////////////////////////
//
// Builtin commands

void builtin_shell(void* ptr, string sCommand, string sParams)
{
	vector<string> v = ChopAndUnquoteString(sParams);

	if(v.size()==0) 
	{
		cerr <<"? GUI internal shell command: No prog/args given."<< endl; 
		return;
	}

	system(sParams.c_str());
}

void builtin_ls(void* ptr, string sCommand, string sParams)
{
	GUI* p = (GUI*)ptr;
	p->ParseLine("shell ls " + sParams);	
}

void builtin_ll(void* ptr, string sCommand, string sParams)
{
	GUI* p = (GUI*)ptr;
	p->ParseLine("shell ls -l " + sParams);	
}

void builtin_try(void* ptr, string sCommand, string sParams)
{
	GUI* p = (GUI*)ptr;
	p->ParseLine(sParams, true);
}

void builtin_exec(void* ptr, string sCommand, string sParams)
{
	GUI* p = (GUI*)ptr;
	p->LoadFile(sParams);
}

void builtin_echo(void* ptr, string sCommand, string sParams)
{
	cout << sParams << endl;
}

void builtin_qmark(void* ptr, string sCommand, string sParams)
{
	cout << "  Perhaps you'd like to type \"commandlist\" or \"gvarlist\"." << endl;
}

void builtin_if(void* ptr, string sCommand, string sParams)
{
  GUI* p = (GUI*)ptr;
  vector<string> v = ChopAndUnquoteString(sParams);
  if(v.size()<2)
    {
      cerr <<"? GUI internal if command: not enough params (syntax: if gvar command)"<< endl; 
      return;
    }

  string sValue = GV3::get_var(v[0]);
  if(sValue == "(not in GVar list)")
    return;
  
  // Have to do this to fix whitespace issues on non-registered gvars
  vector<string> vv = ChopAndUnquoteString(sValue); 
  
  if(vv.size()<1) 
  	return;
  else if(vv[0]!="0")
    {
      string s;
	s = "";
	for(int i=1;i<v.size();i++)
	  s = s + " " +  v[i];
	p->ParseLine(s);
    }
  return;
}

void builtin_ifnot(void* ptr, string sCommand, string sParams)
{
  GUI* p = (GUI*)ptr;
  vector<string> v = ChopAndUnquoteString(sParams);
  if(v.size()<2)
    {
      cerr <<"? GUI internal ifnot command: not enough params (syntax: if gvar command)"<< endl; 
      return;
    }

  string sValue = GV3::get_var(v[0]);
  if(sValue == "(not in GVar list)")
    return;
  
  // Have to do this to fix whitespace issues on non-registered gvars
  vector<string> vv = ChopAndUnquoteString(sValue); 
  
  if(vv.size()<1) 
  	return;
  else if(vv[0]=="0")
    {
      string s;
	s = "";
	for(int i=1;i<v.size();i++)
	  s = s + " " +  v[i];
	p->ParseLine(s);
    }
  return;
}

void builtin_ifeq(void* ptr, string sCommand, string sParams)
{
  GUI* p = (GUI*)ptr;
  vector<string> v = ChopAndUnquoteString(sParams);
  if(v.size()<3)
    {
      cerr <<"? GUI internal ifeq command: not enough params (syntax: if gvar command)"<< endl; 
      return;
    }

  string sValue = GV3::get_var(v[0]);
  string sIfValue = v[1];
  if(sValue == "(not in GVar list)")
    return;
  
  // Have to do this to fix whitespace issues on non-registered gvars
  vector<string> vv = ChopAndUnquoteString(sValue); 
  
  if(vv.size()<1) 
  	return;
  else if(vv[0]==sIfValue)
    {
      string s;
	s = "";
	for(int i=2;i<v.size();i++)
	  s = s + " " +  v[i];
	p->ParseLine(s);
    }
  return;
}

void builtin_toggle(void* ptr, string sCommand, string sParams)
{
  GUI* p = (GUI*)ptr;
  vector<string> v = ChopAndUnquoteString(sParams);
  if(v.size()!=1) 
    {
      cout <<"? GUI internal toggle command: invalid num of params (syntax: toggle gvar)"<< endl; 
      return;
    };
  int nValue = GV3::get<int>(v[0]);
  if(nValue) 
    GV3::set_var(v[0],"0");
  else
    GV3::set_var(v[0],"1");
  return;
  
};

void builtin_set(void* ptr, string sCommand, string sParams)
{
	setvar(sParams);
}


void builtin_gvarlist(void* ptr, string sCommand, string sParams)
{
  bool error = false;
  bool print_all = false;
  string pattern = "";
  
  vector<string> v = ChopAndUnquoteString(sParams);
  if(v.size() > 0  && v[0][0] == '-')
    if(v[0].size() == 2)
      {
	switch(v[0][1])
	  {
	  case 'a':
	    print_all = true;
	    break;
	  default:
	    error = true;
	  }
	if(!error)
	  v.erase(v.begin());
      }
    else
      error = true;
  
  if(v.size()==1)
    pattern = v[0];
  else if(v.size() > 1)
    error = true;
  
  if(error)
    cout << "? GUI internal " << sCommand << ": syntax is " << sCommand << " [-a] [pattern] " << endl;
  else
    GV3::print_var_list(cout, pattern, print_all);
}


void builtin_printvar(void* ptr, string sCommand, string sParams)
{
	cout << sParams << "=" << GV3::get_var(sParams) << endl;;
}


void builtin_commandlist(void* ptr, string sCommand, string sParams)
{
	GUI* p = (GUI*)ptr;

	cout << "  Builtin commands:" << endl;

	for(set<string>::iterator i=p->builtins.begin(); i!=p->builtins.end(); i++)
		cout << "    " << *i << endl;


	cout << "  Registered commands:" << endl;

	for(map<string,CallbackVector>::iterator i=p->mmCallBackMap.begin(); i!=p->mmCallBackMap.end(); i++)
		if(p->builtins.count(i->first) == 0)
			  cout << "    " << i->first << endl;
}

void builtin_queue(void* ptr, string sCommand, string sParams)
{
	vector<string> vs = ChopAndUnquoteString(sParams);
	if(vs.size() < 2)
	  {
	    cout << "? GUI Internal queue command syntax: queue queue-name line-to-enqueue" << endl;
	    return;
	  }
	string &sQueueName = vs[0];
	sParams.erase(sParams.find(sQueueName), sQueueName.length());
	
	GUI* pGUI = (GUI*)ptr;
	pGUI->mmQueues[sQueueName].push_back(sParams);
}

void builtin_runqueue(void* ptr, string sCommand, string sParams)
{
	GUI* pGUI = (GUI*)ptr;
	vector<string> vs = ChopAndUnquoteString(sParams);
	if(vs.size() != 1)
	  {
	    cout << "? GUI Internal " << sCommand << " command syntax: runqueue queue-name " << endl;
	    int nQueues = pGUI->mmQueues.size();
	    
	    cout << "  Currently there are " << nQueues << " queues registered." << endl;
	    if(nQueues > 0)
	      {
		cout << "  They are: ";
		for(map<string,vector<string> >::iterator it=pGUI->mmQueues.begin(); 
		    it!=pGUI->mmQueues.end(); 
		    it++)
		  cout << ((it==pGUI->mmQueues.begin())?"":", ") << it->first;
		cout << endl;
	      }
	    return;
	  }
	string &sQueueName = vs[0];
	vector<string> &vQueue = pGUI->mmQueues[sQueueName];
	for(int i=0; i<vQueue.size(); i++)
	  pGUI->ParseLine(vQueue[i]);
	if(sCommand=="runqueue")
	  vQueue.clear();   // do not clear the queue if the command was runqueue_noclear!
}

///////////////////////////////////////
///////////////////////////////////////
////////     Readline stuff:
///////////////////////////////////////
///////////////////////////////////////


#include <readline/readline.h>
#include <readline/history.h>

void print_history(ostream &ost);
void builtin_history(void* ptr, string sCommand, string sParams)
{
  cout << "History: " << endl;
  print_history(cout);
};

void builtin_save_history(void* ptr, string sCommand, string sParams)
{
  vector<string> v = ChopAndUnquoteString(sParams);
  if(v.size()!=1) 
    cout << "? GUI internal savehistory command: need one param (filename)." << endl;
  else
    {
      ofstream ofs;
      ofs.open(v[0].c_str());
      if(!ofs.good())
	cout << "? GUI internal savehistory command: cannot open " << v[0] << " for write." << endl;
      else
	{
	  print_history(ofs);
	  ofs.close();
	  cout << "  Saved to " << v[0] << endl;
	}
    };
};


char * GUI::ReadlineCommandGeneratorCB(const char *szText, int nState)
{
  return mpReadlineCompleterGUI->ReadlineCommandGenerator(szText, nState);

}

char * GUI::ReadlineCommandGenerator(const char *szText, int nState)
{
  static map<string,CallbackVector>::iterator iRegistered;
  static int nTextLength;
  static int nOffset;
  const char *pcName;
  static char acMyTextCopy[1000];
  
  if(!nState)
  {
	iRegistered = mmCallBackMap.begin();
	nTextLength = strlen(szText);
	nOffset=-1;
  }
  
  while(iRegistered!=mmCallBackMap.end())
    {
      pcName = iRegistered->first.c_str();
      iRegistered++;
      if(strncmp(pcName,szText,nTextLength) == 0)
	{
	  char *t = (char*) malloc(strlen(pcName)+2);
	  if(!t) return NULL;
	  strcpy(t,pcName);
	  t[strlen(pcName)]=' ';
	  t[strlen(pcName)+1]=0;
	  return t;
	};
    };
  
  if(nOffset==-1)
    nOffset = nState;

  return GV3ReadlineCommandGenerator(szText, nState-nOffset);
  
};

int GUI::parseArguments( const int argc, char * argv[], int start, const string prefix, const string execKeyword ){
	while(start < argc){
		string opt = argv[start];
		if(opt.size() > prefix.size() && opt.find(prefix) == 0){
			string name = opt.substr(2);
			if(start < argc-1){
				string value = argv[start+1];
				if(name == execKeyword){
					LoadFile(value);
				} else {
					GV3::set_var(name, value);
				}
				start +=2;
			}
			else break;
		}
		else break;
	}
	return start;
}

void GUI::RegisterBuiltin(string sCommandName, GUICallbackProc callback)
{
	RegisterCommand(sCommandName, callback, this);
	builtins.insert(sCommandName);
}

GUI::GUI()
{
	do_builtins();
}

GUI::GUI(GVars2*)
{
	do_builtins();
}

void GUI::do_builtins()
{
	RegisterBuiltin("shell", builtin_shell);
	RegisterBuiltin("ls", builtin_ls);
	RegisterBuiltin("ll", builtin_ll);
	RegisterBuiltin("try", builtin_try);
	RegisterBuiltin("exec", builtin_exec);
	RegisterBuiltin("echo", builtin_echo);
	RegisterBuiltin("if", builtin_if);
	RegisterBuiltin("ifnot", builtin_ifnot);
	RegisterBuiltin("ifeq", builtin_ifeq);
	RegisterBuiltin("toggle", builtin_toggle);
	RegisterBuiltin("set", builtin_set);
	RegisterBuiltin("history", builtin_history);
	RegisterBuiltin("savehistory", builtin_save_history);
	RegisterBuiltin("?", builtin_qmark);
	RegisterBuiltin("gvarlist", builtin_gvarlist);
	RegisterBuiltin("printvar", builtin_printvar);
	RegisterBuiltin("commandlist", builtin_commandlist);
	RegisterBuiltin("queue",  builtin_queue);
	RegisterBuiltin("runqueue", builtin_runqueue);
	RegisterBuiltin("runqueue_noclear", builtin_runqueue);
};

}

