/***************************************************************************
    date                 : Mar 21 2007
    version              : 0.40
    copyright            : (C) 2004-2007 by Holger Danielsson
    email                : holger.danielsson@versanet.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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "codecompletion.h"

#include <tqregexp.h>
#include <tqfile.h>
#include <tqtimer.h>
#include <tqdict.h>

#include "kiledebug.h"
#include <tdelocale.h>
#include <tdeglobal.h>
#include <tdestandarddirs.h>
#include <tdeconfig.h>
#include <tdemessagebox.h>
#include <tdetexteditor/codecompletioninterface.h>

#include "kileinfo.h"
#include "kileviewmanager.h"
#include "kileconfig.h"
#include "kileedit.h"
#include "kiledebug.h"

namespace KileDocument
{

	static TQRegExp reRef;
	static TQRegExp reRefExt;
	static TQRegExp reCite;
	static TQRegExp reCiteExt;
	static TQRegExp reNotRefChars("[^a-zA-Z0-9_@\\.\\+\\-\\*\\:]");
	static TQRegExp reNotCiteChars("[^a-zA-Z0-9_@\\-\\:]");

	CodeCompletion::CodeCompletion(KileInfo *info) : m_ki(info), m_view(0L)
	{
		m_firstconfig = true;
		m_inprogress = false;
		m_undo = false;
		m_ref = false;
		m_kilecompletion = false;
		m_keylistType = CodeCompletion::ctNone;

		// local abbreviation list
		m_abbrevListview = 0L;
		m_localAbbrevFile = locateLocal("appdata", "complete/abbreviation/", true) + "kile-abbrevs.cwl";

		//reRef.setPattern("^\\\\(pageref|ref|xyz)\\{");
		m_completeTimer = new TQTimer( this );
		connect(m_completeTimer, TQ_SIGNAL( timeout() ), this, TQ_SLOT( slotCompleteValueList() ) );
	}

	CodeCompletion::~CodeCompletion() {}

	bool CodeCompletion::isActive()
	{
		return m_isenabled;
	}

	bool CodeCompletion::inProgress()
	{
		return m_inprogress;
	}

	bool CodeCompletion::autoComplete()
	{
		return m_autocomplete || m_autocompletetext;
	}

	CodeCompletion::Type CodeCompletion::getType()
	{
		return m_type;
	}

	CodeCompletion::Type CodeCompletion::getType( const TQString &text )
	{
		if ( text.find( reRef ) != -1 )
			return CodeCompletion::ctReference;
		else if ( text.find( reCite ) != -1 )
			return CodeCompletion::ctCitation;
		else
			return CodeCompletion::ctNone;
	}

	CodeCompletion::Mode CodeCompletion::getMode()
	{
		return m_mode;
	}

	CodeCompletion::Type CodeCompletion::insideReference(TQString &startpattern)
	{
		if ( m_view->getDoc() )
		{
			uint column = m_view->cursorColumnReal();
			TQString currentline = m_view->getDoc()->textLine(m_view->cursorLine()).left(column);
			int pos = currentline.findRev('\\');
			if ( pos >= 0 )
			{
				TQString command = currentline.mid(pos,column-pos);
				KILE_DEBUG() << "pos=" << pos << ",column=" << column << ",currentline=" << currentline << ",command=" << command << endl;

				if( command.find(reRefExt) != -1 ){
					KILE_DEBUG() << "reRefExt" << endl;
					startpattern = command.right(command.length()-reRefExt.cap(0).length());
					KILE_DEBUG() << "startpattern=" << startpattern << endl;
					if ( startpattern.find(reNotRefChars) == -1 ){
						return CodeCompletion::ctReference ;
					}
				}
 				else if ( command.find(reRef) != -1 ){
					startpattern = command.right(command.length()-reRef.cap(0).length());
					KILE_DEBUG() << "startpattern=" << startpattern << endl;
					if ( startpattern.find(reNotRefChars) == -1 ){
						return CodeCompletion::ctReference ;
					}
				}
				else if( command.find(reCiteExt) != -1 ){
					KILE_DEBUG() << "reCiteExt" << endl;
					startpattern = command.right(command.length()-reCiteExt.cap(0).length());
					KILE_DEBUG() << "startpattern=" << startpattern << endl;
					if ( startpattern.find(reNotCiteChars) == -1 ){
						return CodeCompletion::ctCitation;
					}
				}
				else if ( command.find(reCite) != -1 ){
					startpattern = command.right(command.length()-reCite.cap(0).length());
					KILE_DEBUG() << "startpattern=" << startpattern << endl;
					if ( startpattern.find(reNotCiteChars) == -1 ){
						return CodeCompletion::ctCitation;
					}
				}
			}
		}
		return CodeCompletion::ctNone;
	}

	//////////////////// configuration ////////////////////

	void CodeCompletion::readConfig(TDEConfig *config)
	{
		KILE_DEBUG() << "=== CodeCompletion::readConfig ===================" << endl;

		// save normal parameter
		//KILE_DEBUG() << "   read bool entries" << endl;
		m_isenabled = KileConfig::completeEnabled();
		m_setcursor = KileConfig::completeCursor();
		m_setbullets = KileConfig::completeBullets();
		m_closeenv = KileConfig::completeCloseEnv();
		m_autocomplete = KileConfig::completeAuto();
		m_autocompletetext = KileConfig::completeAutoText();
		m_autocompleteabbrev = KileConfig::completeAutoAbbrev();
		m_latexthreshold = KileConfig::completeAutoThreshold();
		m_textthreshold = KileConfig::completeAutoTextThreshold();
		m_citationMove = KileConfig::completeCitationMove();
		m_autoDollar = KileConfig::autoInsertDollar();

		// we need to read some of Kate's config flags
		readKateConfigFlags(config);

		// reading the wordlists is only necessary at the first start
		// and when the list of files changes
		if ( m_firstconfig || KileConfig::completeChangedLists()  || KileConfig::completeChangedCommands() )
		{
			KILE_DEBUG() << "   set regexp for references..." << endl;
			setReferences();

			KILE_DEBUG() << "   read wordlists..." << endl;
			// wordlists for Tex/Latex mode
			TQStringList files = KileConfig::completeTex();
			setWordlist( files, "tex", &m_texlist );

			// wordlist for dictionary mode
			files = KileConfig::completeDict();
			setWordlist( files, "dictionary", &m_dictlist );

			// wordlist for abbreviation mode
			files = KileConfig::completeAbbrev();
			setWordlist( files, "abbreviation", &m_abbrevlist );

			// remember changed lists
			m_firstconfig = false;
			KileConfig::setCompleteChangedLists(false);
			KileConfig::setCompleteChangedCommands(false);
		}
	}

	void CodeCompletion::readKateConfigFlags(TDEConfig *config)
	{
		config->setGroup("Kate Document Defaults");
		m_autobrackets = ( config->readNumEntry("Basic Config Flags",0) & cfAutoBrackets );
		m_autoindent   = ( config->readNumEntry("Indentation Mode",0) > 0 );
	}

	// save local abbreviation changes
	// (for example before a new configuration should be read)

	void CodeCompletion::saveLocalChanges()
	{
		KILE_DEBUG() << "=== CodeCompletion::saveLocalChanges ===================" << endl;
		m_abbrevListview->saveLocalAbbreviation(m_localAbbrevFile);
	}

	//////////////////// references and citations ////////////////////

	void CodeCompletion::setReferences()
	{
		// build list of references
		TQString references = getCommandList(KileDocument::CmdAttrReference);
		references.replace("*","\\*");
		reRef.setPattern("^\\\\(" + references + ")\\{");
		reRefExt.setPattern("^\\\\(" + references + ")\\{[^\\{\\}\\\\]+,");

		// build list of citations
		TQString citations = getCommandList(KileDocument::CmdAttrCitations);
		citations.replace("*","\\*");
		reCite.setPattern("^\\\\(((c|C|noc)(ite|itep|itet|itealt|itealp|iteauthor|iteyear|iteyearpar|itetext))" + citations +  ")\\{");
		reCiteExt.setPattern("^\\\\(((c|C|noc)(ite|itep|itet|itealt|itealp|iteauthor|iteyear|iteyearpar|itetext))" + citations + ")\\{[^\\{\\}\\\\]+,");
	}

	TQString CodeCompletion::getCommandList(KileDocument::CmdAttribute attrtype)
	{
		TQStringList cmdlist;
		TQStringList::ConstIterator it;

		// get info about user defined references
		KileDocument::LatexCommands *cmd = m_ki->latexCommands();
		cmd->commandList(cmdlist,attrtype,false);

		// build list of references
		TQString commands = TQString();
		for ( it=cmdlist.begin(); it != cmdlist.end(); ++it )
		{
			if ( cmd->isStarredEnv(*it) )
				commands += '|' + (*it).mid(1) + '*';
			commands += '|' + (*it).mid(1);
		}
		return commands;
	}

	//////////////////// wordlists ////////////////////

	void CodeCompletion::setWordlist( const TQStringList &files, const TQString &dir,
	                                  TQValueList<KTextEditor::CompletionEntry> *entrylist
	                                )
	{

		// read wordlists from files
		TQStringList wordlist;
		for ( uint i = 0; i < files.count(); ++i )
		{
			// if checked, the wordlist has to be read
			if ( files[ i ].at( 0 ) == '1' )
			{
				readWordlist( wordlist, dir + '/' + files[i].right( files[i].length()-2 ) + ".cwl", true );
			}
		}

		// add user defined commands and environments
		if ( dir == "tex" )
		{
			addCommandsToTexlist(wordlist);
			setCompletionEntriesTexmode( entrylist, wordlist );
		}
		else if ( dir == "dictionary" )
		{
			wordlist.sort();
			setCompletionEntries( entrylist, wordlist );
		}
		else if ( dir == "abbreviation" )
		{
			// read local wordlist
			TQStringList localWordlist;
			readWordlist(localWordlist, TQString(), false);

			// add local/global wordlists to the abbreviation view
			m_abbrevListview->init(&wordlist,&localWordlist);

			// finally add local wordlists to the abbreviation list
			TQStringList::ConstIterator it;
			for ( it=localWordlist.begin(); it!=localWordlist.end(); ++it )
				wordlist.append( *it );

			wordlist.sort();
			setCompletionEntries( entrylist, wordlist );
		}
	}

	void CodeCompletion::addCommandsToTexlist(TQStringList &wordlist)
	{
		TQStringList cmdlist;
		TQStringList::ConstIterator it;
		KileDocument::LatexCmdAttributes attr;

		// get info about user defined commands and environments
		KileDocument::LatexCommands *cmd = m_ki->latexCommands();
		cmd->commandList(cmdlist,KileDocument::CmdAttrNone,true);

		// add entries to wordlist
		for ( it=cmdlist.begin(); it != cmdlist.end(); ++it )
		{
			if ( cmd->commandAttributes(*it,attr) )
			{
				TQString command,eos;
				TQStringList entrylist;
				if ( attr.type < KileDocument::CmdAttrLabel )          // environment
				{
					command = "\\begin{" + (*it);
					eos = "}";
				}
				else                                                   // command
				{
					command = (*it);
					// eos = TQString();
				}

				// get all possibilities into a stringlist
				entrylist.append( command + eos );
				if ( ! attr.option.isEmpty() )
					entrylist.append( command + eos + "[option]" );
				if ( attr.starred )
				{
					entrylist.append( command + '*' + eos );
					if ( ! attr.option.isEmpty() )
						entrylist.append( command + '*' + eos + "[option]" );
				}

				// finally append entries to wordlist
				TQStringList::ConstIterator itentry;
				for ( itentry=entrylist.begin(); itentry != entrylist.end(); ++itentry )
				{
					TQString entry = (*itentry);
					if ( ! attr.parameter.isEmpty()  )
						entry += "{param}";
					if ( attr.type == KileDocument::CmdAttrList )
						entry += "\\item";
					wordlist.append( entry );
				}
			}
		}
	}

	//////////////////// completion box ////////////////////

	void CodeCompletion::completeWord(const TQString &text, CodeCompletion::Mode mode)
	{
		KILE_DEBUG() << "==CodeCompletion::completeWord(" << text << ")=========" << endl;
		KILE_DEBUG() << "\tm_view = " << m_view << endl;
		if ( !m_view) return;
		KILE_DEBUG() << "ok" << endl;

		// remember all parameters (view, pattern, length of pattern, mode)
		m_text = text;
		m_textlen = text.length();
		m_mode = mode;

		// and the current cursor position
		m_view->cursorPositionReal( &m_ycursor, &m_xcursor );
		m_xstart = m_xcursor - m_textlen;

		// and the current document
		Kate::Document *doc = m_view->getDoc();

		// switch to cmLatex mode, if cmLabel is chosen without any entries
		if ( mode==cmLabel && m_labellist.count()==0 ) {
			TQString s = doc->textLine(m_ycursor);
			int pos = s.findRev("\\",m_xcursor);
			if (pos < 0) {
				KILE_DEBUG() << "\tfound no backslash! s=" << s << endl;
				return;
			}
			m_xstart = pos;
			m_text = doc->text(m_ycursor,m_xstart,m_ycursor,m_xcursor);
			m_textlen = m_text.length();
			m_mode = cmLatex;
		}

		// determine the current list
		TQValueList<KTextEditor::CompletionEntry> list;
		switch ( m_mode )
		{
				case cmLatex: // fall through here
				case cmEnvironment:
				list = m_texlist;
				appendNewCommands(list);
				break;
				case cmDictionary:
				list = m_dictlist;
				break;
				case cmAbbreviation:
				list = m_abbrevlist;
				break;
				case cmLabel:
				list = m_labellist;
				break;
				case cmDocumentWord:
				getDocumentWords(text,list);
				break;
		}

		// is it necessary to show the complete dialog?
		TQString entry, type;
		TQString pattern = ( m_mode != cmEnvironment ) ? text : "\\begin{" + text;
		uint n = countEntries( pattern, &list, &entry, &type );

		KILE_DEBUG() << "entries = " << n << endl;

		// nothing to do
		if ( n == 0 )
			return ;


		// Add a prefix ('\\begin{', length=7) in cmEnvironment mode,
		// because KateCompletion reads from the current line, This also
		// means that the original text has to be restored, if the user
		// aborts the completion dialog
		if ( m_mode == cmEnvironment )
		{
			doc->removeText( m_ycursor, m_xstart, m_ycursor, m_xcursor );
			doc->insertText( m_ycursor, m_xstart, "\\begin{" + m_text );

			// set the cursor to the new position
			m_textlen += 7;
			m_xcursor += 7;
			m_view->setCursorPositionReal( m_ycursor, m_xcursor );

			// set restore mode
			m_undo = true;
		}

		//  set restore mode
		if ( m_mode == cmAbbreviation ) m_undo = true;

		// show the completion dialog
		m_inprogress = true;

		KTextEditor::CodeCompletionInterface *iface;
		iface = dynamic_cast<KTextEditor::CodeCompletionInterface *>( m_view );
		iface->showCompletionBox( list, m_textlen );
	}

	void CodeCompletion::appendNewCommands(TQValueList<KTextEditor::CompletionEntry> & list)
	{
		KTextEditor::CompletionEntry e;
		const TQStringList *ncommands = m_ki->allNewCommands();
		TQStringList::ConstIterator it;
		TQStringList::ConstIterator itend(ncommands->end());
		for ( it = ncommands->begin(); it != itend; ++it )
		{
			e.text = *it;
			list.prepend(e);
		}
	}

	void CodeCompletion::completeFromList(const TQStringList *list,const TQString &pattern)
	{
		KTextEditor::CompletionEntry e;

		KILE_DEBUG() << "completeFromList: " << list->count() << " items" <<   " << pattern=" << pattern<< endl;
		TQStringList sortedlist( *list );
		sortedlist.sort();

		m_labellist.clear();
		TQStringList::ConstIterator it;
		TQStringList::ConstIterator itend(sortedlist.end());
		for ( it = sortedlist.begin(); it != itend; ++it )
		{
			e.text = *it;
			m_labellist.append(  e );
		}

		completeWord(pattern, cmLabel);
	}

	//////////////////// completion was done ////////////////////

	void CodeCompletion::CompletionDone(KTextEditor::CompletionEntry)
	{
		// kile completion: is there a new cursor position?
		if ( m_kilecompletion && m_setcursor && ( m_xoffset != 0 || m_yoffset != 0 ) && m_view )
		{
			int newx = ( m_xoffset != 0 ) ? m_xcursor + m_xoffset - m_textlen : m_xcursor;
			int newy = ( m_yoffset != 0 ) ? m_ycursor + m_yoffset : m_ycursor;

			m_view->setCursorPositionReal( newy, newx );
		}

		m_undo = false;
		m_inprogress = false;
		m_ref = false;
	}

	void CodeCompletion::CompletionAborted()
	{
		KILE_DEBUG() << "CodeCompletion::CompletionAborted()" << endl;
		// aborted: undo if kile completion is active
		if ( m_inprogress && m_undo && m_view )
		{
			uint row, col;
			m_view->cursorPositionReal( &row, &col );

			Kate::Document *doc = m_view->getDoc();
			doc->removeText( m_ycursor, m_xstart, m_ycursor, col );
			doc->insertText( m_ycursor, m_xstart, m_text );

			m_view->setCursorPositionReal( m_ycursor, m_xstart + m_text.length() );
		}

		m_undo = false;
		m_inprogress = false;
		m_ref = false;
	}

	//////////////////// build the text for completion ////////////////////

	// parse an entry for kile completion modes:
	// - delete arguments/parameters
	// - set cursor position
	// - insert bullets

	TQString CodeCompletion::filterCompletionText( const TQString &text, const TQString &type )
	{
		static TQRegExp reEnv = TQRegExp("^\\\\(begin|end)[^a-zA-Z]+");
		KILE_DEBUG() << "   complete filter: " << text << " type " << type << endl;
		m_type = getType( text );    // remember current type

		if ( text!="\\begin{}" && reEnv.search(text)!=-1 )
			m_mode = cmEnvironment;

		// check the cursor position, because the user may have
		// typed some characters or the backspace key. This also
		// changes the length of the current pattern.
		uint row, col;
		m_view->cursorPositionReal( &row, &col );
		if ( m_xcursor != col )
		{
			m_textlen += ( col - m_xcursor );
			m_xcursor = col;
		}

		// initialize offset for the new cursorposition
		m_xoffset = m_yoffset = 0;

		// build the text
		TQString s,prefix;
		Kate::Document *doc = m_view->getDoc();
		TQString textline = doc->textLine(row);
		switch ( m_mode )
		{
				case cmLatex:
				s = buildLatexText( text, m_yoffset, m_xoffset );
				if ( m_autobrackets && textline.at(col)=='}' && m_text.find('{')>=0 )
				{
					doc->removeText(row,col,row,col+1);
				}
				break;
				case cmEnvironment:
				prefix = TQString();
				if ( m_autoindent )
				{
					if ( col-m_textlen>0 )
					{
						prefix = textline.left(col-m_textlen);
						if ( prefix.right(7) == "\\begin{" )
							prefix.truncate(prefix.length()-7);
						else if ( prefix.right(5) == "\\end{" )
							prefix.truncate(prefix.length()-5);
					}
				}
				s = buildEnvironmentText( text, type, prefix, m_yoffset, m_xoffset );
				if ( m_autobrackets && textline.at(col)=='}' && (textline[m_xstart]!='\\' || m_text.find('{')>=0 ) )
				{
					doc->removeText(row,col,row,col+1);
				}
				if ( m_xstart>=7 && textline.mid(m_xstart-7,7) == "\\begin{" )
				{
					m_textlen += 7;
				}
				else if ( m_xstart>=5 && textline.mid(m_xstart-5,5) == "\\end{" )
				{
					m_textlen += 5;
				}
				break;
				case cmDictionary:
				s = text;
				break;
				case cmAbbreviation:
				s = buildAbbreviationText( text );
				break;
				case cmLabel:
				s = buildLabelText( text );
				if ( m_keylistType==CodeCompletion::ctReference
				     || (m_keylistType==CodeCompletion::ctCitation && m_citationMove) )
				{
					m_xoffset = s.length() + 1;
				}
				break;
				case cmDocumentWord:
				s = text;
				break;
        default : s = text; break;
		}

		if ( s.length() > m_textlen )
			return s.right( s.length() - m_textlen );
		else
			return "";
	}

	//////////////////// text in cmLatex mode ////////////////////

	TQString CodeCompletion::buildLatexText( const TQString &text, uint &ypos, uint &xpos )
	{
		return parseText( stripParameter( text ), ypos, xpos, true );
	}

	////////////////////  text in cmEnvironment mode ////////////////////

	TQString CodeCompletion::buildEnvironmentText( const TQString &text, const TQString &type,
	                                              const TQString &prefix, uint &ypos, uint &xpos )
	{
		static TQRegExp reEnv = TQRegExp("^\\\\(begin|end)\\{([^\\}]*)\\}(.*)");

		if (reEnv.search(text) == -1) return text;

		TQString parameter = stripParameter( reEnv.cap(3) );
		TQString start = reEnv.cap(1);
		TQString envname = reEnv.cap(2);
		TQString whitespace = getWhiteSpace(prefix);
		TQString envIndent = m_ki->editorExtension()->autoIndentEnvironment();

		TQString s = "\\" + start + "{" + envname + "}" + parameter + "\n";

		s += whitespace;
		if ( start != "end" )
			s += envIndent;

		bool item = (type == "list" );
		if ( item )
			s += "\\item ";

		if ( m_setbullets && !parameter.isEmpty() )
			s += s_bullet;

		if ( m_closeenv && start != "end" )
			s += '\n' + whitespace + "\\end{" + envname + "}\n";

		// place cursor
		if ( m_setcursor )
		{
			if ( parameter.isEmpty() )
			{
				ypos = 1;
				xpos = whitespace.length() + envIndent.length() + (( item ) ? 6 : 0);
			}
			else
			{
				ypos = 0;
				if( parameter.left(2) == "[<" )
					xpos = 10 + envname.length();
				else
					xpos = 9 + envname.length();
			}
		}

		return s;
	}

	TQString CodeCompletion::getWhiteSpace(const TQString &s)
	{
		TQString whitespace = s;
		for ( uint i=0; i<whitespace.length(); ++i )
		{
			if ( ! whitespace[i].isSpace() )
				whitespace[i] = ' ';
		}
		return whitespace;
	}

	//////////////////// text in  cmAbbreviation mode ////////////////////

	TQString CodeCompletion::buildAbbreviationText( const TQString &text )
	{
		TQString s;

		int index = text.find( '=' );
		if ( index >= 0 )
		{
			// determine text to insert
			s = text.right( text.length() - index - 1 );

			// delete abbreviation
			Kate::Document *doc = m_view->getDoc();
			doc->removeText( m_ycursor, m_xstart, m_ycursor, m_xcursor );
			m_view->setCursorPositionReal( m_ycursor, m_xstart );
			m_xcursor = m_xstart;

			m_textlen = 0;
		}
		else
			s = "";

		return s;
	}

	//////////////////// text in cmLabel mode ////////////////////

	TQString CodeCompletion::buildLabelText( const TQString &text )
	{
		if ( text.at( 0 ) == ' ' )
		{
			// delete space
			Kate::Document * doc = m_view->getDoc();
			doc->removeText( m_ycursor, m_xstart, m_ycursor, m_xstart + 1 );
			m_view->setCursorPositionReal( m_ycursor, m_xstart );
			m_xcursor = m_xstart;

			m_textlen = 0;
			return text.right( text.length() - 1 );
		}
		else
			return text;
	}


	//////////////////// some functions ////////////////////

	TQString CodeCompletion::parseText( const TQString &text, uint &ypos, uint &xpos, bool checkgroup )
	{
		bool foundgroup = false;
		TQString s = "";

		xpos = ypos = 0;
		for ( uint i = 0; i < text.length(); ++i )
		{
			switch ( text[ i ] )
			{
					case '<':
					case '{':
					case '(':
					case '[':		// insert character
					s += text[ i ];
					if ( xpos == 0 )
					{
						// remember position after first brace
						if(text[i] == '[' && (i+1) < text.length() &&  text[i+1] == '<'){
							xpos = i + 2;
							s += text[i+1];
							i++;
						}// special handling for '[<'
						else
							xpos = i + 1;
						// insert bullet, if this is no cursorposition
						if ( ( ! m_setcursor ) && m_setbullets && !( text[i] == '[' && (i+1) < text.length() &&  text[i+1] == '<' ))
							s += s_bullet;
					}
					// insert bullets after following braces
					else if ( m_setbullets && !( text[i] == '[' && (i+1) < text.length() &&  text[i+1] == '<' ) )
						s += s_bullet;
					break;
					case '>':
					case '}':
					case ')':
					case ']':                    // insert character
					s += text[ i ];
					break;
					case ',':                    // insert character
					s += text[ i ];
					// insert bullet?
					if ( m_setbullets )
						s += s_bullet;
					break;
					case '.':      // if the last character is a point of a range operator,
					// it will be replaced by a space or a bullet surrounded by spaces
					if ( checkgroup && ( s.right( 1 ) == "." ) )
					{
						foundgroup = true;
						s.truncate( s.length() - 1 );
						if ( m_setbullets )
							s += ' ' + s_bullet + ' ';
						else
							s += ' ';
					}
					else
						s += text[ i ];
					break;
					default:                      // insert all other characters
					s += text[ i ];
					break;
			}
		}

		// some more work with groups and bullets
		if ( checkgroup && foundgroup && ( m_setbullets | m_setcursor ) )
		{
			int pos = 0;

			// search for braces, brackets and parens
			switch ( TQChar( s[ 1 ] ) )
			{
					case 'l' :
					if ( s.left( 6 ) == "\\left " )
						pos = 5;
					break;
					case 'b' :
					if ( s.left( 6 ) == "\\bigl " )
						pos = 5;
					else if ( s.left( 7 ) == "\\biggl " )
						pos = 6;
					break;
					case 'B' :
					if ( s.left( 6 ) == "\\Bigl " )
						pos = 5;
					else if ( s.left( 7 ) == "\\Biggl " )
						pos = 6;
					break;
			}

			// update cursorposition and set bullet
			if ( pos > 0 )
			{
				if ( m_setcursor )
					xpos = pos;
				if ( m_setbullets )
				{
					if ( ! m_setcursor )
						s.insert( pos, s_bullet );
					s.append( s_bullet );
				}
			}
		}

		// Ergebnis
		return s;
	}

	// strip all names enclosed in braces
	// consider also beamer like stuff [<...>] and <...>

	TQString CodeCompletion::stripParameter( const TQString &text )
	{
		TQString s = "";
		const TQChar *ch = text.unicode();
		bool ignore = false;


		for ( uint i = 0; i < text.length(); ++i )
		{
			switch ( *ch )
			{
					case '[':
					case '{':
					case '(':
					case '<':
					s += *ch;
					ignore = true;
					break;
					case ']':
					case '}':
					case ')':
					case '>':
					s += *ch;
					ignore = false;
					break;
					case ',':
					s += *ch;
					break;
					default:
					if ( ! ignore )
						s += *ch;
					break;
			}
			++ch;
		}
		return s;
	}

	//////////////////// read wordlists  ////////////////////

	void CodeCompletion::readWordlist( TQStringList &wordlist, const TQString &filename, bool global )
	{
		TQString file = ( global )
		             ? TDEGlobal::dirs()->findResource( "appdata", "complete/" + filename )
		             : m_localAbbrevFile;
		if ( file.isEmpty() ) return;

		TQFile f( file );
		if ( f.open( IO_ReadOnly ) )
		{     // file opened successfully
			TQTextStream t( &f );         // use a text stream
			while ( ! t.eof() )
			{        // until end of file...
				TQString s = t.readLine().stripWhiteSpace();       // line of text excluding '\n'
				if ( ! ( s.isEmpty() || s.at( 0 ) == '#' ) )
				{
					wordlist.append( s );
				}
			}
			f.close();
		}
	}

	void CodeCompletion::setCompletionEntries( TQValueList<KTextEditor::CompletionEntry> *list,
	                                           const TQStringList &wordlist )
	{
		// clear the list of completion entries
		list->clear();

		KTextEditor::CompletionEntry e;
		TQStringList::ConstIterator it;

		// build new entries
		for ( it=wordlist.begin(); it != wordlist.end(); ++it )
		{
			// set CompletionEntry
			e.text = *it;
			e.type = "";

			// add new entry
			if ( list->findIndex(e) == -1 )
				list->append(e);
		}
	}

	void CodeCompletion::setCompletionEntriesTexmode( TQValueList<KTextEditor::CompletionEntry> *list,
	        const TQStringList &wordlist )
	{
		// clear the list of completion entries
		list->clear();

		// create a TQMap for a user defined sort
		// order: \abc, \abc[], \abc{}, \abc*, \abc*[], \abc*{}, \abcd, \abcD
		TQStringList keylist;
		TQMap<TQString,TQString> map;

		for ( uint i=0; i< wordlist.count(); ++i )
		{
			TQString s = wordlist[i];
			for ( uint j=0; j<s.length(); ++j )
			{
				TQChar ch = s[j];
				if ( ch.latin1()>='A' && ch.latin1()<='Z' )
					s[j] = (int)ch + 32 ;
				else if ( ch.latin1()>='a' && ch.latin1()<='z' )
					s[j] = (int)ch - 32 ;
				else if ( ch == '}' )
					s[j] = 48;
				else if ( ch == '{' )
					s[j] = 49;
				else if ( ch == '[' )
					s[j] = 50;
				else if ( ch == '*' )
					s[j] = 51;
				else if ( ch == ']' )
					s[j] = 52;
			}
			// don't allow duplicate entries
			if ( ! map.contains(s) )
			{
				map[s] = wordlist[i];
				keylist.append(s);
			}
		}

		// sort mapped keys
		keylist.sort();

		// build new entries: get the sorted keys and insert
		// the real entries, which are saved in the map.
		// if the last 5 chars of an environment are '\item', it is a
		// list environment, where the '\item' tag is also inserted
		KTextEditor::CompletionEntry e;
		TQStringList::ConstIterator it;

		for ( it=keylist.begin(); it != keylist.end(); ++it )
		{
			// get real entry
			TQString s = map[*it];
			if ( s.left( 7 ) == "\\begin{" && s.right( 5 ) == "\\item" )
			{
				e.text = s.left( s.length() - 5 );     // list environment entry
				e.type = "list";
			}
			else
			{
				e.text = s;                        // normal entry
				e.type = "";
			}
			// add new entry (duplicates are impossible)
			list->append(e);
		}
	}

	//////////////////// determine number of entries ////////////////////

	// Count the number of entries. Stop, wenn there are 2 entries,
	// because special functions are only called, when there are 0
	// or 1 entries.

	uint CodeCompletion::countEntries( const TQString &pattern,
	                                   TQValueList<KTextEditor::CompletionEntry> *list,
	                                   TQString *entry, TQString *type )
	{
		TQValueList<KTextEditor::CompletionEntry>::Iterator it;
		uint n = 0;

		for ( it = list->begin(); it != list->end() && n < 2; ++it )
		{
			if ( ( *it ).text.startsWith( pattern ) )
			{
				*entry = ( *it ).text;
				*type = ( *it ).type;
				++n;
			}
		}

		return n;
	}

	TQString CodeCompletion::findExpansion(const TQString &abbrev)
	{
		TQValueList<KTextEditor::CompletionEntry>::Iterator it;

		for ( it=m_abbrevlist.begin(); it!=m_abbrevlist.end(); ++it )
		{
			TQString s = (*it).text;
			int index = s.find("=");
			if ( index>=0 && s.left(index)==abbrev )
				 return s.right( s.length()-index-1 );
		}

		return TQString();
	}

	void CodeCompletion::editComplete(Kate::View *view, Mode mode)
	{
		KILE_DEBUG() << "void CodeCompletion::editComplete(Kate::View *view, Mode "<< mode << ")" << endl;
		KILE_DEBUG() << "m_inprogress=" << m_inprogress << ", isActive()=" << isActive() << endl;
		m_view = view; 

		if ( !m_view || !isActive() )
			return ;
		
		m_inprogress=true;

		KILE_DEBUG() << "proceeded" << endl;
		// check for a special case: call from inside of a reference command
		if ( mode==cmLatex )
		{
			TQString startpattern;
			CodeCompletion::Type reftype = insideReference(startpattern);
			if ( reftype != CodeCompletion::ctNone )
			{
				m_ref = true;
				editCompleteList(reftype,startpattern);
				return;
			}
		}

		TQString word;
		Type type;
		if ( getCompleteWord(( mode == cmLatex ) ? true : false, word, type ) )
		{
			if ( mode == cmLatex && word.at( 0 ) != '\\' )
			{
				mode = cmDictionary;
			}

			if ( type == CodeCompletion::ctNone )
				completeWord(word, mode);
			else
				editCompleteList(type);
		}
	}

	void CodeCompletion::editCompleteList(Type type,const TQString &pattern)
	{
		KILE_DEBUG() << "==editCompleteList=============" << endl;
		m_keylistType = type;
		if ( type == ctReference )
			completeFromList(info()->allLabels(),pattern);
		else if ( type == ctCitation )
			completeFromList(info()->allBibItems(),pattern);
		else
		{
			m_keylistType = CodeCompletion::ctNone;
			kdWarning() << "unsupported type in CodeCompletion::editCompleteList" << endl;
		}
	}

	//////////////////// slots for code completion ////////////////////

	void CodeCompletion::slotCompletionDone(KTextEditor::CompletionEntry entry)
	{
		KILE_DEBUG() << "==slotCompletionDone (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
		CompletionDone(entry);
		
		// if kile completion was active, look if we need to show an additional list
		if ( m_kilecompletion )
		{
			m_kilecompletion = false;
			if ( getMode() == cmLatex )
			{
				m_type = getType(entry.text);
				if ( (m_type==CodeCompletion::ctReference && info()->allLabels()->count()>0)  ||
					  (m_type==CodeCompletion::ctCitation  && info()->allBibItems()->count()>0) )
				{
					m_keylistType = m_type;
					m_ref = true;
		 			m_completeTimer->start(20,true);
				}
			}
		}
	}

	void CodeCompletion::slotCompleteValueList()
	{
		KILE_DEBUG() << "==slotCompleteValueList (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
		m_completeTimer->stop();
		editCompleteList(getType());
	}

	void CodeCompletion::slotCompletionAborted()
	{
		KILE_DEBUG() << "==slotCompletionAborted (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
		CompletionAborted();
	}

	void CodeCompletion::slotFilterCompletion( KTextEditor::CompletionEntry* c, TQString *s )
	{
		KILE_DEBUG() << "==slotFilterCompletion (" << m_kilecompletion << "," << m_inprogress << ")=============" << endl;
		if ( inProgress() )                 // dani 28.09.2004
		{
			KILE_DEBUG() << "\tin progress: s=" << *s << endl;
			*s = filterCompletionText( c->text, c->type );
			KILE_DEBUG() << "\tfilter --->" << *s << endl;
			m_inprogress = false;
			m_kilecompletion = true;
		}
	}

	void CodeCompletion::slotCharactersInserted(int, int, const TQString& string )
	{
		KILE_DEBUG() << "==slotCharactersInserted (m_kilecompletion=" << m_kilecompletion << "m_inprogress," << m_inprogress << ",m_ref=" << m_ref << ",string=" << string << ")=============" << endl;

		if ( !inProgress() && m_autoDollar && string=="$" )
		{
			autoInsertDollar();
			return;
		}

		// only work, if autocomplete mode of Kile is active
		if ( !isActive() || !autoComplete() )
			return ;

		//FIXME this is not very efficient
		m_view = info()->viewManager()->currentTextView();

		// try to autocomplete abbreviations after punctuation symbol
		if ( !inProgress() && m_autocompleteabbrev && completeAutoAbbreviation(string) )
			return;

		// rather unsusual, but it may happen: the cursor is inside
		// of a reference command without a labellist.
		if ( ! m_ref )
		{
			TQString startpattern;
			CodeCompletion::Type reftype = insideReference(startpattern);
			if ( reftype != CodeCompletion::ctNone )
			{
				m_ref = true;
				editCompleteList(reftype,startpattern);
				return;
			}
		} // also unusual but may also happen, 20.11.2008 tbraun
		else if( m_ref && !inProgress())
		{
			TQString startpattern;
			CodeCompletion::Type reftype = insideReference(startpattern);
			if ( reftype == CodeCompletion::ctNone )
			{
				m_ref = false;
				return;
			}
		}

		TQString word;
		Type type;
		bool found = ( m_ref ) ? getReferenceWord(word) : getCompleteWord(true,word,type );
		if ( found )
		{
			int wordlen = word.length();
			KILE_DEBUG() << "   auto completion: word=" << word << " mode=" << m_mode << " inprogress=" << inProgress() << endl;
			if ( inProgress() )               // continue a running mode?
			{
				KILE_DEBUG() << "   auto completion: continue current mode" << endl;
				completeWord(word, m_mode);
			}
			else if ( word.at( 0 )=='\\' && m_autocomplete && wordlen>=m_latexthreshold)
			{
				KILE_DEBUG() << "   auto completion: latex mode" << endl;
				if ( string.at( 0 ).isLetter() )
				{
					completeWord(word, cmLatex);
				}
				else if ( string.at( 0 ) == '{' )
				{
					editCompleteList(type);
				}
			}
			else if ( word.at(0).isLetter() && m_autocompletetext && wordlen>=m_textthreshold)
			{
				KILE_DEBUG() << "   auto completion: document mode" << endl;
				completeWord(word,cmDocumentWord);
			}
		}
	}

	//////////////////// testing characters (dani) ////////////////////

	static bool isBackslash ( TQChar ch )
	{
		return ( ch == '\\' );
	}

	bool CodeCompletion::getCompleteWord(bool latexmode, TQString &text, Type &type )
	{
		if ( !m_view ) return false;

		uint row, col;
		TQChar ch;

		// get current position
		m_view->cursorPositionReal( &row, &col );

		// there must be et least one sign
		if ( col < 1 )
			return "";

		// get current text line
		TQString textline = m_view->getDoc()->textLine( row );

		//
		int n = 0;                           // number of characters
		int index = col;                     // go back from here
		while ( --index >= 0 )
		{
			// get current character
			ch = textline.at( index );

			if ( ch.isLetter() || ch=='.' || ch == '_' || ch.isDigit() || ( latexmode && ( index + 1 == ( int ) col ) && ch == '{' ) )
				++n;                           // accept letters and '{' as first character in latexmode
			else
			{
				if ( latexmode && isBackslash( ch ) && oddBackslashes( textline, index ) )         // backslash?
					++n;
				break;                         // stop when a backslash was found
			}
		}

		// select pattern and set type of match
		text = textline.mid( col - n, n );
		type = getType( text );

		return !text.isEmpty();
	}

	bool CodeCompletion::getReferenceWord(TQString &text)
	{
		if ( !m_view ) return false;

		uint row, col;
		TQChar ch;

		// get current position
		m_view->cursorPositionReal( &row, &col );
		// there must be et least one sign
		if ( col < 1 )
			return false;

		// get current text line
		TQString textline = m_view->getDoc()->textLine( row );

		// search the current reference string
		int pos = textline.findRev(reNotRefChars,col-1);
		if ( pos < 0 )
			pos = 0;

		// select pattern
		text = textline.mid(pos+1,col-1-pos);
		return ( (uint)pos < col-1 );
	}

	void CodeCompletion::getDocumentWords(const TQString &text,
	                                      TQValueList<KTextEditor::CompletionEntry> &list)
	{
		KILE_DEBUG() << "getDocumentWords: " << endl;
		list.clear();

		TQRegExp reg("(\\\\?\\b" + TQString(text[0]) + "[^\\W\\d_]+)\\b");
		Kate::Document *doc = m_view->getDoc();

		TQString s;
		KTextEditor::CompletionEntry e;
		TQDict<bool> seen;
		bool alreadyseen = true;

		for (uint i=0; i<doc->numLines(); ++i) {
			s = doc->textLine(i);
			int pos = 0;
			while ( pos >= 0 ) {
				pos = reg.search(s,pos);
				if ( pos >= 0 ) {
					if ( reg.cap(1).at(0)!='\\' && text!=reg.cap(1) && !seen.find(reg.cap(1)) ) {
						e.text = reg.cap(1);                        // normal entry
						e.type = "";
						list.append( e );
						seen.insert(reg.cap(1),&alreadyseen);
					}
					pos += reg.matchedLength();
				}
			}
		}
	}

	//////////////////// counting backslashes (dani) ////////////////////

	bool CodeCompletion::oddBackslashes( const TQString& text, int index )
	{
		uint n = 0;
		while ( index >= 0 && isBackslash( text.at( index ) ) )
		{
			++n;
			--index;
		}
		return ( n % 2 ) ? true : false;
	}

	//////////////////// complete auto abbreviation ////////////////////

	bool CodeCompletion::completeAutoAbbreviation(const TQString &text)
	{
		if ( text.length() != 1 )
			return false;

		TQChar ch = text[0];
		if ( ! (ch.isSpace() || ch.isPunct()) )
			return false;

		uint row,col;
		m_view->cursorPositionReal( &row, &col );
		TQString abbrev = getAbbreviationWord(row,col-1);
		if ( abbrev.isEmpty() )
			return false;

		TQString expansion = findExpansion(abbrev);
		if ( expansion.isEmpty() )
			return false;

		KILE_DEBUG() << "=== CodeCompletion::completeAutoAbbreviation: abbrev=" << abbrev << "  exp=" << expansion << endl;

		uint len = abbrev.length();
		uint startcol = col - len - 1;
		Kate::Document *doc = m_view->getDoc();
		doc->removeText( row,startcol,row,startcol+abbrev.length()+1 );
		doc->insertText( row,startcol,expansion+ch);
		m_view->setCursorPositionReal( row,startcol+expansion.length()+1 );

		return true;
	}

	TQString CodeCompletion::getAbbreviationWord(uint row, uint col)
	{
		TQString textline = m_view->getDoc()->textLine( row );

		int index = (int)col;
		while ( --index >= 0 )
		{
			TQChar ch = textline.at( index );
			if ( ! ch.isLetterOrNumber() )
				break;
		}

		return textline.mid(index+1,col-index-1);
	}

	//////////////////// connection with the abbreviation listview  ////////////////////

	void CodeCompletion::setAbbreviationListview(KileAbbrevView *listview)
	{
		m_abbrevListview = listview;

		connect( m_abbrevListview, TQ_SIGNAL(updateAbbrevList(const TQString &, const TQString &)),
		         this, TQ_SLOT(slotUpdateAbbrevList(const TQString &, const TQString &)) );

	}

	void CodeCompletion::slotUpdateAbbrevList(const TQString &ds, const TQString &as)
	{
		if ( ! ds.isEmpty() )
		{
			deleteAbbreviationEntry(ds);
		}
		if ( ! as.isEmpty() )
		{
			addAbbreviationEntry(as);
		}
	}

	void CodeCompletion::deleteAbbreviationEntry( const TQString &entry )
	{
		KILE_DEBUG() << "=== CodeCompletion::deleteAbbreviationEntry (" << entry << ")" << endl;
		TQValueList<KTextEditor::CompletionEntry>::Iterator it;
		for ( it=m_abbrevlist.begin(); it!=m_abbrevlist.end(); ++it )
		{
			if ( (*it).text == entry )
			{
				m_abbrevlist.remove( it );
				return;
			}
		}
	}

	void CodeCompletion::addAbbreviationEntry( const TQString &entry )
	{
		KILE_DEBUG() << "=== CodeCompletion::addAbbreviationEntry (" << entry << ")" << endl;
		TQValueList<KTextEditor::CompletionEntry>::Iterator it;
		for ( it=m_abbrevlist.begin(); it!=m_abbrevlist.end(); ++it )
		{
			if ( (*it).text > entry )
				break;
		}

		KTextEditor::CompletionEntry e;
		e.text = entry;
		e.type = TQString();

		if ( it == m_abbrevlist.begin() )
			m_abbrevlist.prepend(e);
		else if ( it == m_abbrevlist.end() )
			m_abbrevlist.append(e);
		else
			m_abbrevlist.insert(it,e);
	}

	//////////////////// autoinsert $ ////////////////////

	void CodeCompletion::autoInsertDollar()
	{
		Kate::View *view = info()->viewManager()->currentTextView();
		if ( view )
		{
			uint row,col;
			view = info()->viewManager()->currentTextView();
			view->cursorPositionReal( &row, &col );
			view->getDoc()->insertText(row,col,"$");
			view->setCursorPositionReal( row, col );
		}
	}

}

#include "codecompletion.moc"
