/***************************************************************************
 *   Copyright (C) 2004 by Jens Dagerbo                                    *
 *   jens.dagerbo@swipnet.se                                               *
 *                                                                         *
 *   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 <tqwhatsthis.h>
#include <tqpopupmenu.h>
#include <tqtextstream.h>
#include <tqfile.h>
#include <tqregexp.h>

#include <klineedit.h>
#include <tdelistview.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdeparts/part.h>
#include <tdetexteditor/document.h>
#include <tdetexteditor/editinterface.h>
#include <tdetexteditor/viewcursorinterface.h>
#include <kprocess.h>
#include <kdebug.h>
#include <kstringhandler.h>
#include <kdialogbase.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <tdeaction.h>

#include "kdevappfrontend.h"
#include <kdevgenericfactory.h>
#include <kdevcore.h>
#include <kdevmainwindow.h>
#include <kdevproject.h>
#include <kdevpartcontroller.h>
#include <kdevplugininfo.h>
#include "configwidgetproxy.h"
#include "domutil.h"
#include "kdeveditorutil.h"

#include "ctags2_settingswidget.h"
#include "ctags2_widget.h"
#include "ctags2_part.h"
#include "tags.h"

#define CTAGSSETTINGSPAGE 1

namespace ctags
{
#include "readtags.h"
}

typedef KDevGenericFactory<CTags2Part> CTags2Factory;
static const KDevPluginInfo pluginData("kdevctags2");
K_EXPORT_COMPONENT_FACTORY( libkdevctags2, CTags2Factory( pluginData ) )

CTags2Part::CTags2Part(TQObject *parent, const char *name, const TQStringList& )
  : KDevPlugin(&pluginData, parent, name ? name : "ctags2Part" )
{
	setInstance(CTags2Factory::instance());
	setXMLFile("kdevpart_ctags2.rc");

	TQDomDocument & dom = *projectDom();
	TQString customTagFile = DomUtil::readEntry( dom, "/ctagspart/customTagfilePath" );
	if ( customTagFile.isEmpty() )
	{
		customTagFile =  project()->projectDirectory() + "/tags";
	}
	TQStringList tagFiles = DomUtil::readListEntry(dom, "/ctagspart/activeTagsFiles", "file");
	tagFiles.push_front(customTagFile);

	Tags::setTagFiles(tagFiles);

	m_widget = new CTags2Widget(this);

	TQWhatsThis::add(m_widget, i18n("<b>CTags</b><p>Result view for a tag lookup. Click a line to go to the corresponding place in the code."));
	m_widget->setCaption(i18n("CTags Lookup"));
	mainWindow()->embedOutputView( m_widget, i18n( "CTags" ), i18n( "CTags lookup results" ) );

	connect( core(), TQT_SIGNAL(contextMenu(TQPopupMenu *, const Context *)), this, TQT_SLOT(contextMenu(TQPopupMenu *, const Context *)) );

	_configProxy = new ConfigWidgetProxy( core() );
	_configProxy->createProjectConfigPage( i18n("CTags"), CTAGSSETTINGSPAGE, info()->icon() );
	connect( _configProxy, TQT_SIGNAL(insertConfigWidget(const KDialogBase*, TQWidget*, unsigned int )),
		this, TQT_SLOT(insertConfigWidget(const KDialogBase*, TQWidget*, unsigned int )) );

	new TDEAction( i18n("Lookup Current Text"), 0, CTRL+Key_Underscore, this, TQT_SLOT(slotLookup()), actionCollection(), "ctags_lookup_shortcut");
	new TDEAction( i18n("Lookup Current Text as Declaration"), 0, CTRL+Key_Semicolon, this, TQT_SLOT(slotLookupDeclaration()), actionCollection(), "ctags_declaration_shortcut");
	new TDEAction( i18n("Lookup Current Text as Definition"), 0, CTRL+Key_Colon, this, TQT_SLOT(slotLookupDefinition()), actionCollection(), "ctags_definition_shortcut");
	new TDEAction( i18n("Jump to Next Match"), 0, 0, this, TQT_SLOT(slotGoToNext()), actionCollection(), "ctags_jump_to_next");
	new TDEAction( i18n("Open Lookup Dialog"), 0, 0, this, TQT_SLOT(slotOpenLookup()), actionCollection(), "ctags_input_shortcut");
}


CTags2Part::~CTags2Part()
{
	if ( m_widget )
	{
		mainWindow()->removeView( m_widget );
	}
	delete m_widget;
	delete _configProxy;
}

void CTags2Part::insertConfigWidget( const KDialogBase * dlg, TQWidget * page, unsigned int pagenumber )
{
	if ( pagenumber == CTAGSSETTINGSPAGE )
	{
		CTags2SettingsWidget * w = new CTags2SettingsWidget( this, page );
		connect( dlg, TQT_SIGNAL(okClicked()), w, TQT_SLOT(slotAccept()) );
		connect( w, TQT_SIGNAL(newTagsfileName(const TQString& )), this, TQT_SLOT(updateTagsfileName(const TQString& )) );
	}
}

void CTags2Part::updateTagsfileName( const TQString & )
{
	m_widget->updateDBDateLabel();
}

// wrapper for creating a tag file for the current project
bool CTags2Part::createTagsFile()
{
	// check if user specified a custom tag file name
	TQDomDocument & dom = *projectDom();
	TQString tagsFileCustom = DomUtil::readEntry( dom, "/ctagspart/customTagfilePath" ).stripWhiteSpace();

	return createTagsFile(tagsFileCustom, project()->projectDirectory());
}

// creates a new tag file with the specified name for the specified source directory
bool CTags2Part::createTagsFile(const TQString& tagFile, const TQString& dir)
{
/*
	TDEProcess proc;
	proc.setWorkingDirectory( project()->projectDirectory() );

	proc << "ctags";
	proc << "-R" << "--c++-types=+px" << "--excmd=pattern" << "--exclude=Makefile";

	bool success = proc.start(TDEProcess::Block);

	return success;
*/

	// get name of the ctags binary
	TDEConfig * config = kapp->config();
	config->setGroup( "CTAGS" );
	TQString ctagsBinary = config->readEntry( "ctags binary" ).stripWhiteSpace();
	if ( ctagsBinary.isEmpty() )
	{
		ctagsBinary = "ctags";
	}

	// set a default argument list
	TQString argsDefault = "-R --c++-types=+px --excmd=pattern --exclude=Makefile --exclude=.";

	TQDomDocument & dom = *projectDom();
	TQString argsCustom = DomUtil::readEntry( dom, "/ctagspart/customArguments" ).stripWhiteSpace();
	TQString commandline = ctagsBinary + " " + ( argsCustom.isEmpty() ? argsDefault : argsCustom ) + ( tagFile.isEmpty() ? "" : " -f " + tagFile );
	commandline += " ";
	commandline += dir;

	if (KDevAppFrontend *appFrontend = extension<KDevAppFrontend>("TDevelop/AppFrontend"))
		appFrontend->startAppCommand(dir, commandline, false);

	return true;
}

void CTags2Part::contextMenu(TQPopupMenu *popup, const Context *context)
{
	if (!context->hasType( Context::EditorContext ))
		return;

	const EditorContext *econtext = static_cast<const EditorContext*>(context);
	TQString ident = econtext->currentWord();
	if (ident.isEmpty())
		return;

	TDEConfig * config = kapp->config();
	config->setGroup( "CTAGS" );
	bool showDeclaration = config->readBoolEntry( "ShowDeclaration", true );
	bool showDefinition = config->readBoolEntry( "ShowDefinition", true );
	bool showLookup = config->readBoolEntry( "ShowLookup", true );

	if ( Tags::hasTag( ident ) && ( showDefinition || showDeclaration || showLookup  ) )
	{
		m_contextString = ident;
	    TQString squeezed = KStringHandler::csqueeze(ident, 30);

		popup->insertSeparator();

		if ( showDeclaration )
			popup->insertItem( i18n("CTags - Go to Declaration: %1").arg(squeezed), this, TQT_SLOT(slotGotoDeclaration()) );

		if ( showDefinition )
			popup->insertItem( i18n("CTags - Go to Definition: %1").arg(squeezed), this, TQT_SLOT(slotGotoDefinition()) );

		if ( showLookup )
			popup->insertItem( i18n("CTags - Lookup: %1").arg(squeezed), this, TQT_SLOT(slotGotoTag()) );
	}
}

void CTags2Part::showHits( Tags::TagList const & tags )
{
	m_widget->displayHitsAndClear( tags );

	mainWindow()->raiseView( m_widget );
	m_widget->output_view->setFocus();
}

void CTags2Part::slotGotoTag( )
{
	showHits( Tags::getExactMatches( m_contextString ) );
}

void CTags2Part::gotoTagForTypes( TQStringList const & types )
{
	Tags::TagList list = Tags::getMatches( m_contextString, false, types );

	if ( list.count() < 1 ) return;

	TDEConfig * config = kapp->config();
	config->setGroup("CTAGS");
	bool jumpToFirst = config->readBoolEntry( "JumpToFirst", false );

	if ( list.count() == 1 || jumpToFirst )
	{
		Tags::TagEntry tag = list.first();
		KURL url;
		TQString fileWithTagInside;
		// assume relative path to project directory if path does not start with slash
		if (tag.file[0] != '/') {
			fileWithTagInside = project()->projectDirectory() + "/" + tag.file;
		}
		else {
			fileWithTagInside = tag.file;
		}
		url.setPath(fileWithTagInside);
		partController()->editDocument( url, getFileLineFromPattern( url, tag.pattern ) );
		m_widget->displayHitsAndClear( list );
	}
	else
	{
		showHits( list );
	}
}

void CTags2Part::slotGotoDefinition( )
{
	TQStringList types;
	types << "S" << "d" << "f" << "t" << "v";
	gotoTagForTypes( types );
}

void CTags2Part::slotGotoDeclaration( )
{
	TQStringList types;
	types << "L" << "c" << "e" << "g" << "m" << "n" << "p" << "s" << "u" << "x";
	gotoTagForTypes( types );
}


int CTags2Part::getFileLineFromStream( TQTextStream & istream, TQString const & pattern )
{
	if ( pattern.isEmpty() ) return -1;

	// ctags interestingly escapes "/", but apparently nothing else. lets revert that
	TQString unescaped = pattern;
	unescaped.replace( "\\/", "/" );

	// most of the time, the ctags pattern has the form /^foo$/
	// but this isn't true for some macro definitions
	// where the form is only /^foo/
	// I have no idea if this is a ctags bug or not, but we have to deal with it

	TQString reduced, escaped, re_string;
	if ( unescaped.endsWith( "$/" ) )
	{
		reduced = unescaped.mid( 2, unescaped.length() -4 );
		escaped = TQRegExp::escape( reduced );
		re_string = TQString( "^" + escaped + "$" );
	}
	else
	{
		reduced = unescaped.mid( 2, unescaped.length() -3 );
		escaped = TQRegExp::escape( reduced );
		re_string = TQString( "^" + escaped );
	}

	TQRegExp re( re_string );

	int n = 0;
	while ( !istream.atEnd() )
	{
		if ( re.search( istream.readLine() ) > -1 )
		{
			return n;
		}
		n++;
	}
	return -1;
}

int CTags2Part::getFileLineFromPattern( KURL const & url, TQString const & pattern )
{
	// if the file is open - get the line from the editor buffer
	if ( KTextEditor::EditInterface * ei = dynamic_cast<KTextEditor::EditInterface*>( partController()->partForURL( url ) ) )
	{
		TQString ibuffer = ei->text();
		TQTextStream istream( &ibuffer, IO_ReadOnly );
		return getFileLineFromStream( istream, pattern );
	}
	else // else the file is not open - get the line from the file on disk
	{
		TQFile file( url.path() );
		TQString buffer;

		if ( file.open( IO_ReadOnly ) )
		{
			TQTextStream istream( &file );
			return getFileLineFromStream( istream, pattern );
		}
	}
	return -1;
}

void CTags2Part::slotLookupDeclaration( )
{
 	m_contextString = KDevEditorUtil::currentWord( dynamic_cast<KTextEditor::Document*>( partController()->activePart() ) );
	if ( !m_contextString.isEmpty() )
	{
		slotGotoDeclaration();
	}
}

void CTags2Part::slotLookupDefinition( )
{
 	m_contextString = KDevEditorUtil::currentWord( dynamic_cast<KTextEditor::Document*>( partController()->activePart() ) );
	if ( !m_contextString.isEmpty() )
	{
		slotGotoDefinition();
	}
}

void CTags2Part::slotLookup( )
{
 	m_contextString = KDevEditorUtil::currentWord( dynamic_cast<KTextEditor::Document*>( partController()->activePart() ) );
	if ( !m_contextString.isEmpty() )
	{
		slotGotoTag();
	}
}

void CTags2Part::slotOpenLookup( )
{
	mainWindow()->raiseView( m_widget );
	m_widget->input_edit->setFocus();
}

void CTags2Part::slotGoToNext( )
{
	m_widget->goToNext();
}

#include "ctags2_part.moc"
