/***************************************************************************
 *   Copyright (C) 2006 by Luis Quintela   *
 *   lois_qu@web.de   *
 *                                                                         *
 *   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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
//BEGIN CLASS SkWidget
#include "skwidget.h"
#include "skxmlloader.h"




#include <qlayout.h>
#include <qlineedit.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qstring.h>
#include <qstrlist.h>//meibe not needed
#include <qfiledialog.h>
#include <klocale.h>
#include <kprocess.h>
#include <qmap.h>
#include <qcursor.h>
#include <stdlib.h>
#include <kglobal.h>
#include <kstandarddirs.h>
//BEGIN OF PUBLIC METHODES

SkWidget::SkWidget(QWidget * parent ,const int checkboxes)
: QWidget(parent, "skwidget" ),
main(0),
midle(0),
buttons(0),
console(0),
open_file(0),
open_dir(0),
man(0),
info(0),
list(0),
number_of_checkboxes(checkboxes%2==0?checkboxes:checkboxes+1),
dir(),
commList(),
commListNames(),
command(),
current_command("man"),
state(searchingFirstPos),
available(TRUE),
autoPaste(FALSE),
pressed(FALSE),
parsed(FALSE),
pipeline(FALSE), //it has first no function
optsPos(0),
optsBoundPos(0),
argsPos(0),
commPos(0)


{
	//variables init--------------
	
	
	KStandardDirs *dirs=KGlobal::dirs();
	QString path=dirs->findResource("data","sk_konsole/CommandList.xml");
	loadCommandsFromXMLFile(path);
	comms=QRegExp(commListNames);
	pipe=QRegExp(PIPE);
	args=QRegExp(ARGS);
	
	
	//-------------------------------------------
	
	//Layouts
	main= new QGridLayout(this,ROWS+checkboxes,COLLS,MARGIN,SPACE,"main");
	midle= new QGridLayout;
	buttons = new QVBoxLayout(SPACING,"Buttons");
	//widgets
	info = new QLabel(this,"info");
	console=new QLineEdit(this,"console");
	open_file = new QPushButton(i18n("&file"),this,"openFile");
	open_dir = new QPushButton(i18n("&dir"),this,"openDir");
	man  = new QPushButton(i18n("&man"),this,"info");


	makeCheckBoxes();
	makeGUI();	
	initCheckBoxes();
	connect(open_file,SIGNAL(clicked()),this,SLOT(openFile()));
	connect(open_dir,SIGNAL(clicked()),this,SLOT(openDir()));
	connect(man,SIGNAL(clicked()),this,SLOT(openManual()));
	connect(console,SIGNAL(textChanged ( const QString & )),this,SLOT(textChanged ( const QString & )));
	connect(console,SIGNAL(returnPressed()),this,SLOT(doReturn()));
	
}


SkWidget::~SkWidget()
{
	delete list;

}

//END OF PUBLIC MEMBERS 



//BEGIN OF SLOTS



//BEGIN SLOTS FOR THE WORK WITH THE COMMAND OPTIONS (checkboxes)

void SkWidget::initCheckBoxes()
{//inits the checkboxes	
	available=FALSE;//to avoid a call of control methods
	for(int i=0;i<number_of_checkboxes;i++)
	{
		list[i]->setEnabled(false);
		if(list[i]->isChecked())
			list[i]->setChecked (false);
		list[i]->opt=QString::null;
		list[i]->setText(QString::null);
		list[i]->hide();
	}
	available=TRUE;
}

void SkWidget::changedOption(const bool checked,const QString& opt)
{//this slot does the main work for the graphical input by checkboxes
	if(available)// not function when initCheckBoxes is working
	{
	const QString p=command.opts_map->find(opt).data().prefix;
	SkOption *currentOpt=&command.opts_map->find(opt).data();
		if(checked) //insert option in the text
		{		
			if(currentOpt->friends!=0&&pressed)
			{	
				QString k=QString::null;		
				FriendOptions::Iterator fr;	
				pressed=FALSE;		
				for ( fr = currentOpt->friends->begin(); fr != currentOpt->friends->end(); ++fr )
				{
					k= fr.key();
					int i=getIndexOfOpt(k);
					if(i>-1&&i<number_of_checkboxes)
						list[i]->setEnabled(FALSE);
						if(list[i]->isChecked())
							list[i]->setChecked(FALSE);
				}			
			}else pressed=FALSE;
			if(!parsed)//setting checkbox by comm-line no insert in comm-line 
			{
			const QString o =" "+p+opt;
			setInOptionsPart(o);
			}
		}else //delete de option from text
		{
			if(currentOpt->friends!=0&&pressed)
			{
				FriendOptions::Iterator fr;
				for ( fr = currentOpt->friends->begin(); fr != currentOpt->friends->end(); ++fr )
				{
					int i=getIndexOfOpt(fr.key());
					if(i>-1&&i<number_of_checkboxes)
						list[i]->setEnabled(TRUE);
				}
				
			}
			pressed=FALSE;
			if(!parsed)//setting checkbox by comm-line no delete from comm-line 
			{
	
				QRegExp *trx=&currentOpt->rexp;
				QString txt=getOptionsPart();
				if(trx->search(txt)!=-1)
				{
					QRegExp exact_reg=QRegExp(),group_reg=QRegExp();			
					int length,first;
					QString old_regexp=QString::null; 
					old_regexp=	trx->pattern();
					old_regexp= old_regexp.remove(0,10);
					if(command.bound_simple_ops)
						group_reg.setPattern(old_regexp);
					old_regexp= old_regexp.remove(2,1);
					old_regexp= old_regexp.remove(13,3);
					old_regexp.append("\\b");
					exact_reg.setPattern(old_regexp); //info->setText(exact_reg.pattern());
					bool has_argument=currentOpt->argument;		
					while(exact_reg.search(txt)!=-1)
					{
						length=exact_reg.matchedLength();
						first=exact_reg.pos();				
						if(has_argument && args.search(txt.right(txt.length()-(first+length)))!=-1)	
							length+=args.matchedLength();
						txt.replace(first,length,QString::null);	
					}				
						if(	group_reg.search(txt)!=-1 &&
							command.bound_simple_ops &&
							opt.length()==1 &&
							p.length()==1 &&
							(length=group_reg.matchedLength())>opt.length()+p.length())
						{
							
							first=group_reg.pos();
							txt.replace(first+length-1,1,QString::null);		
						}				
					setInOptionsPart(txt,TRUE);
				}// else info->setText(trx->errorString()+"\n"+trx->pattern());DEBUG Information
			}
		}	

	}
	
}

//END OF SLOTS FOR THE WORK WITH THE COMMAND OPTIONS (checkboxes)

//BEGIN SLOTS FOR THE WORK WITH THE BUTTONS 

void SkWidget::openManual()
{//opens the man of the current command page in Konqueror
	KProcess *konqueror = new KProcess;
	QString man_page(MAN_PROTOCOL+ current_command);
	*konqueror << KONQUEROR;
	*konqueror << man_page;
	konqueror->start();
	
}

void SkWidget::openFile()
{//opens a file dialog	
	//TODO net transparency
	//TODO KDE integration ??
	QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
    fd->setMode( QFileDialog::ExistingFile );
	fd->setDir(dir);
	QString s;
    if ( fd->exec() == QDialog::Accepted )
        s = fd->selectedFile();
	dir=s;		
	setInArgsPart(" "+dir); 
	delete fd;
}

void SkWidget::openDir()
{//opens a directory dialog	
	//TODO net transparency
	//TODO KDE integration ??
	QString s = QFileDialog::getExistingDirectory(
                    dir,
                    this,
                    "get existing directory",
                    "Choose a directory",
                    TRUE );	

	dir=s;		
	setInArgsPart(" "+dir);
}

//END OF SLOTS FOR THE WORK WITH THE  BUTTONS



void SkWidget::textChanged( const QString & txt )
{//controls the parse function
	
	if(!autoPaste && !pressed)
	{
		if((comms.search(console->text(),commPos))==-1 )	
			init();
// 		else
// 			HERE MUST BE THE CONTROL CODE OF PIPELINES	
		switch(state)
		{
			case searchingFirstPos:
			{	
				initCheckBoxes();	
				searchFirstPos(txt,commPos);
				break;
			}
			case takingCommand:
			{
				matchCommand(txt,commPos);
				break;
			}
	
			case takingOpts:
			{			
				matchOption(txt,optsBoundPos,argsPos);
				break;
			}
		}
//BEGIN DEBUG 
// 		info->setText("ANGEKOMMEN "+txt+" PosC="+QString::number(console->cursorPosition())
// 		+"\nstate= "+QString::number((int)state)
// 		+"\ncommPos="+QString::number(commPos)
// 		+"\noptsBoundPos= "+QString::number(optsBoundPos)
// 		+"\noptsPos= "+QString::number(optsPos)
// 		+"\nargsPos= "+QString::number(argsPos)
// 		+"\n "+console->text().right(console->text().length()-optsPos).replace(" ","$")
// 		+"\n "+txt.right(txt.length()-optsPos).replace(" ","$")
// 			);//DEBUG Information
//END DEBUG 		//////////////////////////////////////////////////////

	}

}

void SkWidget::doReturn( )
{//calls the Konsole with the comm-line and inits all
	if(state==takingOpts)
	{
		KProcess *proc = new KProcess;
		//*proc << "konsole";
		
		QStringList  input=QStringList::split(" ",console->text());
		for ( QStringList::Iterator it = input.begin(); it != input.end(); ++it )	
			*proc << (*it);
		proc->start();
		//proc->resume();
	}
	console->setText(QString::null);

}


//END OF SLOTS

//BEGIN Of PROTECTED CLASS MEMBERS
int SkWidget::getIndexOfOpt(const QString searched_opt)
{//searchs the index of a SkChekbox in the list
	int i=-1;
	int comp=-1;
	while(++i<number_of_checkboxes){
		comp=QString::compare(searched_opt,list[i]->opt);	
		if( comp== 0)return i;
	}

	return -1;
}



//END OF PROTECTED CLASS MEMBERS

//BEGIN OF PRIVATE CLASS MEMBERS

void SkWidget::init(bool pipe )
{//inits the parse state
// 	if(!pipe)
// 	{ 
	info->clear();
	current_command=QString::null;
	state=searchingFirstPos;
	available=TRUE;
	autoPaste=FALSE;
	pipeline=FALSE;
	optsPos=0;
	optsBoundPos=0;
	argsPos=0;
	commPos=0;
// 	}
// 	else
// 	{later it will do part of the support FOR PIPELINES
// 	}
}

void SkWidget::makeGUI( )
{//builds the GUI
	
	main->addMultiCellLayout(midle,/*FROM*/SECOND_ROW+number_of_checkboxes,/*TO*/SECOND_ROW+number_of_checkboxes,
									/*FROM*/FIRST_COLL,/*TO*/SECOND_COLL);
	main->addMultiCellWidget(console,/*FROM*/THIRD_ROW+number_of_checkboxes,/*TO*/THIRD_ROW+number_of_checkboxes,
							/*FROM*/FIRST_COLL,/*TO*/SECOND_COLL);

	buttons->addWidget(open_file);
	buttons->addWidget(open_dir);
	buttons->addWidget(man);

	midle->addMultiCellWidget(info,/*FROM*/FIRST_ROW,/*TO*/FIRST_ROW,/*FROM*/FIRST_COLL,/*TO*/SECOND_COLL);
	midle->addLayout(buttons,FIRST_ROW,SECOND_COLL);
		
	
	
	info->setFrameStyle( QFrame::Panel | QFrame::Sunken );
	
	open_dir->setFixedSize(open_dir->sizeHint());
	
	setMinimumWidth (650);
	setMinimumHeight(250);
	
	
	show();
}

void SkWidget::makeCheckBoxes( )
{//builds the SkCheckBoxes
	list= new SkCheckBox*[number_of_checkboxes];
	const int mid=(int)(number_of_checkboxes/2);
	for(int i=0;i<number_of_checkboxes;i++)
	{	
	 	char name[3]={i<mid?'l':'r',(char)(abs(i%mid)+48),'\0'};
		*(list+i)=new SkCheckBox("",this,name);
		main->addWidget(*(list+i),abs(i%mid),i<mid?0:1);
		connect(list[i],SIGNAL(changed(const bool,const QString&)),
				this,	SLOT(changedOption(const bool,const QString&)));
		connect(list[i],SIGNAL(pressed ()),
				this,	SLOT(skCheckBoxPressed()));
	}
	list[mid]->setMinimumWidth ( SKCHECKBOX_MINIMUM_SIZE);
	list[0]->setMinimumWidth ( SKCHECKBOX_MINIMUM_SIZE); 


}

bool SkWidget::loadCommandsFromXMLFile( QString path )
{//loads the information about the selected commands from XML-file
	QFile file(path);
	QXmlSimpleReader reader;
	SkXMLLoader loader(&commList,&commListNames);
	reader.setContentHandler(&loader);
	reader.setErrorHandler(&loader);
	bool result=reader.parse(&file);
	file.close();
	return result;

}

void SkWidget::makeCommandGUI()
{

		SkCommands::Iterator it=commList.find(current_command);
		QString res=QString::null;
		if(it==commList.end())
		{
			current_command=EMPTY_COMMAND;
			res.append(EMPTY_COMMAND_COMMENT);
		}
		else{
			const QString a=it.key();
			const QString b= it.data().comment; 
			res.append(i18n(b));
			if(it.data().opts_map!=0)
			{
				SkOptions::Iterator ops;
				int i=0;
				available=FALSE;
				for ( ops = it.data().opts_map->begin(); ops != it.data().opts_map->end(); ++ops ) 
				{
					list[i]->opt=ops.key() ;
					list[i]->setText(ops.data().prefix+"&"+ops.key()+" "+ops.data().widgetComment);
					
					list[i]->setEnabled(TRUE);
					list[i]->show();
					++i; 
					
				}
				available=TRUE;
			}
		}
 
	info->setText(i18n(res));
}
void SkWidget::setInOptionsPart(QString opt,bool set)
{//to mask the options part of a comand line 
	autoPaste=TRUE;//to avoid a recursively call of textChanged() 
	
	if(set)//mainly from deleting a opt by checkbox
	{
		QString tmp=console->text().replace(optsPos,argsPos-optsPos,opt);
		console->setText(tmp);
		argsPos=optsPos+opt.length();
	}
	else//mainly from inserting a opt by checkbox
	{
		if(opt.contains('-')==1)
		{
			console->setCursorPosition(optsPos);
			optsBoundPos+=opt.length();
			argsPos+=opt.length();
		}
		else
		{
			console->setCursorPosition(optsBoundPos);
			argsPos+=opt.length();
		}
				
		console->insert(opt);
		
	}
	console->setCursorPosition(console->text().length());
	autoPaste=FALSE;
}
QString SkWidget::getOptionsPart( )
{//this method masks the comm-line	
	return console->text().mid(optsPos,argsPos-optsPos);
}
void SkWidget::setInArgsPart(QString arg)
{//this method only masks the insert of paths
 	autoPaste=TRUE;
	console->insert(arg);
	autoPaste=FALSE;
}
void SkWidget::searchFirstPos( const QString & txt,int pos )
{//this method was maked in order to support pipelines
//however this programm now doesn't support pipelines
	if(pipeline)
	{
		int p=0;
		while ( p >= 0 ) {
			p = pipe.search( txt, p );
			if ( p >= 0 ) {
				commPos=p;
				p += pipe.matchedLength();      // move along in txt
			}
		}
	}
	
	state=takingCommand; //but this is needed for parsing
	matchCommand(txt,commPos);
	
}
void SkWidget::matchOption( const QString & txt ,int offset,int end )
{//parses command's options
	QString optsPart=txt.right(txt.length()-optsPos);	
	int p=0,t=0;
 	QRegExp opts=QRegExp();
 	QString o=QString::null;
	optsBoundPos=calculatePos(optsPart,SINGLE_OPTS,optsPos);
	t=calculatePos(txt.right(txt.length()-optsBoundPos),OPTS,optsBoundPos);
	p=calculatePos(txt.right(txt.length()-optsBoundPos),command.no_prefix_opts,optsBoundPos);
	argsPos=t>p?t:p;
	p=0;
	for ( QStringList::Iterator it = command.opts_names.begin(); it != command.opts_names.end(); ++it )
	{
		o=*it;
		p=getIndexOfOpt(o);
		if(command.bound_simple_ops)
			opts.setPattern("\\s+\\-\\w*"+o+"\\w*");
		else
			opts.setPattern("\\s+\\-"+o);
		if(p>-1)
		if(!list[p]->isChecked() && opts.search(optsPart)!=-1)//found and not checked
		{ 			
			parsed=TRUE;
			pressed=TRUE;
			list[p]->setChecked(TRUE);
			parsed=FALSE;
		}else
		if(list[p]->isChecked()&&opts.search(optsPart)==-1)//not found and not checked
		{ 
			parsed=TRUE;
			pressed=TRUE;
			list[p]->setChecked(FALSE);
			parsed=FALSE;
		}	
	}
	p=0;o=QString::null;
	int q=0;
	for ( QStringList::Iterator long_it = command.long_opts_names.begin(); long_it != command.long_opts_names.end(); ++long_it )
	{
		o=*long_it;
		q=o.contains('-');
		o.remove('-');
		p=getIndexOfOpt(o);
		while(--q>-1)o.prepend("\\-");
		opts.setPattern("\\s+"+o);
		
		if(p>-1)
		if(!list[p]->isChecked() && opts.search(optsPart)!=-1)//found and not checked
		{ 			
			parsed=TRUE;
			pressed=TRUE;
			list[p]->setChecked(TRUE);
			parsed=FALSE;
		}else
		if(list[p]->isChecked()&&opts.search(optsPart)==-1)//not found and not checked
		{ 
			parsed=TRUE;
			pressed=TRUE;
			list[p]->setChecked(FALSE);
			parsed=FALSE;
		}	
	}
}

void SkWidget::matchCommand( const QString & txt,int pos )
{//parses commands

	if(comms.search(txt,pos)!=-1)
	{
		command=commList.find(comms.cap(0)).data();
		current_command=comms.cap(0);
		commPos=comms.pos();
		optsPos=commPos+comms.matchedLength();
		optsBoundPos=optsPos;
		argsPos=optsPos;
		makeCommandGUI();
		state=takingOpts;
		matchOption(txt,optsPos,argsPos);
	}
}

int SkWidget::calculatePos(QString txt,QString regexp,uint firstPos )
{//search the last position of a part of a comm-line with offset firstPos
	int p=0,result=firstPos;
	QRegExp rx=QRegExp(regexp);
	QString o=QString::null;
	while ( p >= 0 ) {
 		p = rx.search( txt, p );
 		if ( p >= 0 ) {
 			result=firstPos+p+rx.matchedLength();
			p+=rx.matchedLength();
		}
	}
	return result;
}


//END OF PRIVATE MEMBERS

#include "skwidget.moc"
//END CLASS SkWidget

//BEGIN SKCHECKBOX CLASS




SkCheckBox::SkCheckBox(const QString & text, QWidget * parent, const char * name):QCheckBox(text,parent,name)
{
	opt=QString::null;
	connect(this,SIGNAL(toggled(bool)),this,SLOT(changedBox(bool)));
}


SkCheckBox::~SkCheckBox()
{
}

void  SkCheckBox::changedBox(bool b )
{		
	emit changed(b,opt);	
}



//END SKCHECKBOX CLASS

