/***************************************************************************
 *   Copyright (C) 1999-2001 by Bernd Gehrmann                             *
 *   bernd@kdevelop.org                                                    *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <tqdir.h>
#include <tqlayout.h>
#include <tqregexp.h>
#include <tqpainter.h>
#include <tqtoolbutton.h>
#include <kdialogbase.h>
#include <tdelocale.h>
#include <kprocess.h>
#include <tdeparts/part.h>
#include <tdetexteditor/selectioninterface.h>
#include <tdeaction.h>
#include <tdepopupmenu.h>
#include <ktabwidget.h>
#include <kpushbutton.h>
#include <kiconloader.h>
#include <tdemessagebox.h>
using namespace KTextEditor;

#include "kdevcore.h"
#include "kdevproject.h"
#include "kdevmainwindow.h"
#include "kdevpartcontroller.h"

#include "grepdlg.h"
#include "grepviewpart.h"
#include "grepviewwidget.h"


class GrepListBoxItem : public ProcessListBoxItem
{
public:
	GrepListBoxItem(const TQString &fileName, const TQString &lineNumber, const TQString &text, bool showFilename);
	TQString filename()
	{ return fileName; }
	int linenumber()
	{ return lineNumber.toInt(); }
	virtual bool isCustomItem();

private:
	virtual void paint(TQPainter *p);
	TQString fileName, lineNumber, text;
	bool show;
};


GrepListBoxItem::GrepListBoxItem(const TQString &fileName, const TQString &lineNumber, const TQString &text, bool showFilename)
		: ProcessListBoxItem( TQString(), Normal),
		fileName(fileName), lineNumber(lineNumber), text(text.stripWhiteSpace()),
		show(showFilename)
{
	this->text.replace( TQChar('\t'), TQString("  ") );
}


bool GrepListBoxItem::isCustomItem()
{
	return true;
}


void GrepListBoxItem::paint(TQPainter *p)
{
	TQColor base, dim, result, bkground;
	if (listBox()) {
		const TQColorGroup& group = listBox()->palette().active();
		if (isSelected()) {
			bkground = group.button();
			base = group.buttonText();
		}
		else
		{
			bkground = group.base();
			base = group.text();
		}
		dim = blend(base, bkground);
		result = group.link();
	}
	else
	{
		base = TQt::black;
		dim = TQt::darkGreen;
		result = TQt::blue;
		if (isSelected())
			bkground = TQt::lightGray;
		else
			bkground = TQt::white;
	}
	TQFontMetrics fm = p->fontMetrics();
	TQString stx = lineNumber + ":  ";
	int y = fm.ascent()+fm.leading()/2;
	int x = 3;
	p->fillRect(p->window(), TQBrush(bkground));
	if (show)
	{
		p->setPen(dim);
		p->drawText(x, y, fileName);
		x += fm.width(fileName);
	}
	else
	{
		p->setPen(base);
		TQFont font1(p->font());
		TQFont font2(font1);
		font2.setBold(true);
		p->setFont(font2);
		p->drawText(x, y, stx);
		p->setFont(font1);
		x += fm.width(stx);

		p->setPen(result);
		p->drawText(x, y, text);
	}
}


GrepViewWidget::GrepViewWidget(GrepViewPart *part) :  TQWidget(0, "grepview widget")
{
	m_layout = new TQHBoxLayout(this, 0, -1, "greplayout");

	m_tabWidget = new KTabWidget(this);

	m_layout->add(m_tabWidget);

	m_curOutput = new GrepViewProcessWidget(m_tabWidget);

	m_tabWidget->addTab(m_curOutput, i18n("Search Results"));

	grepdlg = new GrepDialog( part, this, "grep widget");
	connect( grepdlg, TQ_SIGNAL(searchClicked()), this, TQ_SLOT(searchActivated()) );
	connect( m_curOutput, TQ_SIGNAL(processExited(TDEProcess* )), this, TQ_SLOT(slotSearchProcessExited()) );

	connect( m_tabWidget, TQ_SIGNAL(currentChanged(TQWidget*)), this, TQ_SLOT(slotOutputTabChanged()) );

	connect( m_curOutput, TQ_SIGNAL(clicked(TQListBoxItem*)),
	         this, TQ_SLOT(slotExecuted(TQListBoxItem*)) );
	connect( m_curOutput, TQ_SIGNAL(returnPressed(TQListBoxItem*)),
	         this, TQ_SLOT(slotExecuted(TQListBoxItem*)) );


	connect( m_curOutput, TQ_SIGNAL(contextMenuRequested( TQListBoxItem*, const TQPoint&)), this, TQ_SLOT(popupMenu(TQListBoxItem*, const TQPoint&)));
	m_part = part;

	m_closeButton = new TQToolButton(m_tabWidget);//@todo change text/icon
	m_closeButton->setIconSet(SmallIconSet("tab_remove"));
	m_closeButton->setEnabled(false);

	connect (m_closeButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(slotCloseCurrentOutput()));
	m_tabWidget->setCornerWidget(m_closeButton);
}


GrepViewWidget::~GrepViewWidget()
{}


void GrepViewWidget::showDialog()
{
	// Get the selected text if there is any
	KParts::ReadOnlyPart *ro_part = dynamic_cast<KParts::ReadOnlyPart*>(m_part->partController()->activePart());
	if (ro_part)
	{
		SelectionInterface *selectIface = dynamic_cast<SelectionInterface*>(ro_part);
		if(selectIface && selectIface->hasSelection())
		{
			TQString selText = selectIface->selection();
			if(!selText.contains('\n'))
			{
				grepdlg->setPattern(selText);
			}
		}
	}
	// Determine if we have a list of project files
	KDevProject *openProject = m_part->project();
	if (openProject)
	{
		grepdlg->setEnableProjectBox(!openProject->allFiles().isEmpty());
	}
	else
	{
		grepdlg->setEnableProjectBox(false);
	}
	grepdlg->show();
}

// @todo - put this somewhere common - or just use TQt if possible
static TQString escape(const TQString &str)
{
	TQString escaped("[]{}()\\^$?.+-*|");
	TQString res;

	for (uint i=0; i < str.length(); ++i)
	{
		if (escaped.find(str[i]) != -1)
			res += "\\";
		res += str[i];
	}

	return res;
}


void GrepViewWidget::showDialogWithPattern(TQString pattern)
{
	// Before anything, this removes line feeds from the
	// beginning and the end.
	int len = pattern.length();
	if (len > 0 && pattern[0] == '\n')
	{
		pattern.remove(0, 1);
		len--;
	}
	if (len > 0 && pattern[len-1] == '\n')
		pattern.truncate(len-1);
	grepdlg->setPattern( pattern );

	// Determine if we have a list of project files
	KDevProject *openProject = m_part->project();
	if (openProject)
	{
		grepdlg->setEnableProjectBox(!openProject->allFiles().isEmpty());
	}
	else
	{
		grepdlg->setEnableProjectBox(false);
	}
	grepdlg->show();
}


void GrepViewWidget::searchActivated()
{
	if ( grepdlg->keepOutputFlag() )
		slotKeepOutput();

	m_tabWidget->showPage( m_curOutput );

	m_curOutput->setLastFileName("");
	m_curOutput->setMatchCount( 0 );

	TQString command, files;

	// waba: code below breaks on filenames containing a ',' !!!
	TQStringList filelist = TQStringList::split(",", grepdlg->filesString());

	if (grepdlg->useProjectFilesFlag())
	{
		KDevProject *openProject = m_part->project();
		if (openProject)
		{
			TQString tmpFilePath;
			TQStringList projectFiles = openProject->allFiles();
			if (!projectFiles.isEmpty())
			{
				tmpFilePath = openProject->projectDirectory() + TQChar(TQDir::separator()) + ".grep.tmp";
				TQString dir = grepdlg->directoryString(), file;
				TQValueList<TQRegExp> regExpList;

				if (dir.endsWith(TQChar(TQDir::separator())))
					dir.truncate(dir.length() - 1);

				if (!filelist.isEmpty())
				{
					for (TQStringList::Iterator it = filelist.begin(); it != filelist.end(); ++it)
						regExpList.append(TQRegExp(*it, true, true));
				}

				m_tempFile.setName(tmpFilePath);
				if (m_tempFile.open(IO_WriteOnly))
				{
					TQTextStream out(&m_tempFile);
					for (TQStringList::Iterator it = projectFiles.begin(); it != projectFiles.end(); ++it)
					{
						file = TQDir::cleanDirPath(openProject->projectDirectory() + TQChar(TQDir::separator()) + *it);

						TQFileInfo info(file);
						if (grepdlg->recursiveFlag() && !info.dirPath(true).startsWith(dir)) continue;
						if (!grepdlg->recursiveFlag() && info.dirPath(true) != dir) continue;

						bool matchOne = regExpList.count() == 0;
						for (TQValueList<TQRegExp>::Iterator it2 = regExpList.begin(); it2 != regExpList.end() && !matchOne; ++it2)
							matchOne = (*it2).exactMatch(file);

						if (matchOne)
							out << KShellProcess::quote(file) + "\n";
					}

					m_tempFile.close();
				}
				else
				{
					KMessageBox::error(this, i18n("Unable to create a temporary file for search."));
					return;
				}
			}

			command = "cat ";
			command += tmpFilePath.replace(' ', "\\ ");
		}
	}
	else
	{
		if (!filelist.isEmpty())
		{
			TQStringList::Iterator it(filelist.begin());
			files = KShellProcess::quote(*it);
			++it;
			for (; it != filelist.end(); ++it)
				files += " -o -name " + KShellProcess::quote(*it);
		}

		TQString filepattern = "find ";
		filepattern += KShellProcess::quote(grepdlg->directoryString());
		if (!grepdlg->recursiveFlag())
			filepattern += " -maxdepth 1";
		filepattern += " \\( -name ";
		filepattern += files;
		filepattern += " \\) -print -follow";
		if (grepdlg->noFindErrorsFlag())
			filepattern += " 2>/dev/null";

		command = filepattern + " " ;
	}

	TQStringList excludelist = TQStringList::split(",", grepdlg->excludeString());
	if (!excludelist.isEmpty())
	{
		TQStringList::Iterator it(excludelist.begin());
		command += "| grep -v ";
		for (; it != excludelist.end(); ++it)
			command += "-e " + KShellProcess::quote(*it) + " ";
	}

	if (!grepdlg->useProjectFilesFlag())
	{
		// quote spaces in filenames going to xargs
		command += "| sed \"s/ /\\\\\\ /g\" ";
	}

	command += "| xargs " ;

#ifndef USE_SOLARIS
	command += "egrep -H -n -s ";
#else
	// -H reported as not being available on Solaris,
	// but we're buggy without it on Linux.
	command += "egrep -n ";
#endif

	if (!grepdlg->caseSensitiveFlag())
	{
		command += "-i ";
	}
	command += "-e ";

	m_lastPattern = grepdlg->patternString();
	TQString pattern = grepdlg->templateString();
	if (grepdlg->regexpFlag())
		pattern.replace(TQRegExp("%s"), grepdlg->patternString());
	else
		pattern.replace(TQRegExp("%s"), escape( grepdlg->patternString() ) );
	command += KShellProcess::quote(pattern);
	m_curOutput->startJob("", command);

	m_part->mainWindow()->raiseView(this);
	m_part->core()->running(m_part, true);
}


void GrepViewWidget::slotExecuted(TQListBoxItem* item)
{
	ProcessListBoxItem *i = static_cast<ProcessListBoxItem*>(item);
	if (!i || !i->isCustomItem())
		return;

	GrepListBoxItem *gi = static_cast<GrepListBoxItem*>(i);
	m_part->partController()->editDocument( KURL( gi->filename() ), gi->linenumber()-1 );
}


void GrepViewProcessWidget::insertStdoutLine(const TQCString &line)
{
	int pos;
	TQString filename, linenumber, rest;

	TQString str;
        if( !grepbuf.isEmpty() )
        {
            str = TQString::fromLocal8Bit( grepbuf+line );
            grepbuf.truncate( 0 );
        }else
        {
            str = TQString::fromLocal8Bit( line );
        }
	if ( (pos = str.find(':')) != -1)
	{
		filename = str.left(pos);
		str.remove( 0, pos+1 );
		if ( ( pos = str.find(':') ) != -1)
		{
			linenumber = str.left(pos);
			str.remove( 0, pos+1 );
			// filename will be displayed only once
			// selecting filename will display line 1 of file,
			// otherwise, line of requested search
			if ( _lastfilename != filename )
			{
				_lastfilename = filename;
				insertItem(new GrepListBoxItem(filename, "0", str, true));
				insertItem(new GrepListBoxItem(filename, linenumber, str, false));
			}
			else
			{
				insertItem(new GrepListBoxItem(filename, linenumber, str, false));
			}
			maybeScrollToBottom();
		}
		m_matchCount++;
	}
}


void GrepViewWidget::projectChanged(KDevProject *project)
{
	TQString dir = project? project->projectDirectory() : TQDir::homeDirPath();
	grepdlg->setDirectory(dir);
}

void GrepViewWidget::popupMenu(TQListBoxItem*, const TQPoint& p)
{
	if(m_curOutput->isRunning()) return;

	TDEPopupMenu rmbMenu;

	if(TDEAction *findAction = m_part->actionCollection()->action("edit_grep"))
	{
		rmbMenu.insertTitle(i18n("Find in Files"));
		findAction->plug(&rmbMenu);
		rmbMenu.exec(p);
	}
}

void GrepViewWidget::slotKeepOutput( )
{
	if ( m_lastPattern == TQString() ) return;

	m_tabWidget->changeTab(m_curOutput, m_lastPattern);

	m_curOutput = new GrepViewProcessWidget(m_tabWidget);
	m_tabWidget->insertTab(m_curOutput, i18n("Search Results"), 0);

	connect( m_curOutput, TQ_SIGNAL(clicked(TQListBoxItem*)), this, TQ_SLOT(slotExecuted(TQListBoxItem*)) );
	connect( m_curOutput, TQ_SIGNAL(returnPressed(TQListBoxItem*)), this, TQ_SLOT(slotExecuted(TQListBoxItem*)) );
	connect( m_curOutput, TQ_SIGNAL(processExited(TDEProcess* )), this, TQ_SLOT(slotSearchProcessExited()) );
	connect( m_curOutput, TQ_SIGNAL(contextMenuRequested( TQListBoxItem*, const TQPoint&)), this, TQ_SLOT(popupMenu(TQListBoxItem*, const TQPoint&)));
}

void GrepViewWidget::slotCloseCurrentOutput( )
{
	ProcessWidget* pw = static_cast<ProcessWidget*>(m_tabWidget->currentPage());
	if (pw == m_curOutput)
		return;

	m_tabWidget->removePage(pw);
	delete pw;

	if (m_tabWidget->count() == 1)
		m_closeButton->setEnabled( false );
}

void GrepViewWidget::killJob( int signo )
{
	m_curOutput->killJob( signo );

	if (!m_tempFile.name().isEmpty() && m_tempFile.exists())
		m_tempFile.remove();
}

bool GrepViewWidget::isRunning( ) const
{
	return m_curOutput->isRunning();
}

void GrepViewWidget::slotOutputTabChanged( )
{
	ProcessWidget* pw = static_cast<ProcessWidget*>(m_tabWidget->currentPage());
	if (pw == m_curOutput)
		m_closeButton->setEnabled( false );
	else
		m_closeButton->setEnabled( true );
}

void GrepViewWidget::slotSearchProcessExited( )
{
	m_part->core()->running(m_part, false);

	if (!m_tempFile.name().isEmpty() && m_tempFile.exists())
		m_tempFile.remove();
}

void GrepViewProcessWidget::childFinished( bool normal, int status )
{
	// When xargs executes grep several times (because the command line
	// generated would be too large for a single grep) and one of those
	// sets of files passed to grep does not contain a match, then an
	// error status of 123 is set by xargs, even if there is a match in
	// another set of files.
	// Reset this false status here.

	if (status == 123 && numRows() > 1)
		status = 0;

	insertItem(new ProcessListBoxItem(i18n("*** %n match found. ***", "*** %n matches found. ***", m_matchCount), ProcessListBoxItem::Diagnostic));
	maybeScrollToBottom();

	ProcessWidget::childFinished(normal, status);
}

void GrepViewProcessWidget::addPartialStdoutLine(const TQCString & line)
{
    grepbuf += line;
}


#include "grepviewwidget.moc"
