/* Copyright (C) 2002 
	 Philippe Fremy <pfremy@kde.org>
	 Mickael Marchand <marchand@kde.org>
	 Eray Ozkural <erayo@cs.bilkent.edu.tr>

	 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; see the file COPYING.  If not, write to
	 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
	 Boston, MA 02111-1307, USA.
	 */

#include "vimwidget.h"
#include "vimdocument.h"
#include "vimview.h"
#include "vimpart_factory.h"
#include "vimpartbrowserextension.h"

#include <kapplication.h>
#include <kdebug.h>
#include <dcopclient.h>
#include <klocale.h>
#include <kstandarddirs.h>
#include <unistd.h>
#include <qregexp.h>

using namespace std;

namespace Vim {

	struct HLMode{
		QString name;
		QString section;
		QStringList extensions;
	};


	class Cursor: public KTextEditor::Cursor
	{
		public:
			Cursor(Document* d): doc(d) { }

			virtual ~Cursor(){ }

			virtual void position ( unsigned int *line, unsigned int *col ) const {
				QString text = doc->activeWidget()->evalExpr("line(\".\")");
				*line = text.toUInt();
				(*line)--;
				text = doc->activeWidget()->evalExpr("col(\".\")");
				*col = text.toUInt();
				(*col)--;
			}

			virtual bool setPosition(unsigned int line, unsigned int col) {
				line++; col++;
				QString cmd=QString("call cursor(%1,%2)").arg((int)line).arg((int)col);
				doc->activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			virtual bool insertText(const QString& text) {
				unsigned int line, col;
				position(&line,&col);
				doc->insertText(line,col,text);
				return true;
			}

			virtual bool removeText(unsigned int numberOfCharacters) {
				//this stops removing at EOL, this is not good XXX
				unsigned int line, col;
				position(&line,&col);
				QString cmd=QString("normal %1x").arg(numberOfCharacters);
				doc->activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			virtual QChar currentChar() const {
				char c;
				QString text = doc->activeWidget()->evalExpr("getline(\".\")[col(\".\")-1]");
				return text.unicode()[0];
			}
			
		private:
			Document* doc;
	};

	Document::Document( bool bReadOnly, bool bSingleView, bool bWantBrowser, QWidget *parentWidget, const char *widgetName, QObject *parent, const char *n) 
		: KTextEditor::Document (parent,n)
			{
				activeView = 0;		
				setInstance( VimPartFactory::instance() );

				m_singleView = bSingleView;
				m_readOnly = bReadOnly;
				m_browserView = bWantBrowser;

				if (bWantBrowser) browser = new VimpartBrowserExt(this);
				
				if (bSingleView) {
					KTextEditor::View *v = createView ( parentWidget, "VimWidget" );
					if (bReadOnly) setReadWrite(false);
					insertChildClient(v);
					v->show();
					setWidget(v);
				}

				//signals connection now
//				receiver = new VimDCOP(this);
			}

			Document::~Document()
			{
				kdDebug() << "Document destructor" << endl;
//				delete receiver;
			}

			VimWidget* Document::activeWidget() const
			{
				return ((View*)activeView)->vwidget();
			}

			KTextEditor::View *Document::createView ( QWidget *parent, const char *name )
			{
				View* view = new View ( this , parent );
/*				while (view->vwidget()->hasStarted()==false) {
					kapp->processEvents();
				}*/
				kdDebug() << "View ready" << endl;
				addView(view);
				return view;
			}

			QPtrList<KTextEditor::View> Document::views () const
			{
				return _views;
			}

			void Document::addView (KTextEditor::View *view)
			{
				_views.append(view);
				activeView=view;
			}

			void Document::removeView (KTextEditor::View *view)
			{
				if (activeView==view) 
					activeView=0;
				_views.removeRef (view);
			}

			void Document::setReadWrite( bool rw )
			{
				QString vimReadonlyVar = "readonly";
				if (rw == true) {
					vimReadonlyVar.prepend("no");
				}
				activeWidget()->setVimVariable(vimReadonlyVar);
			}

			bool Document::isReadWrite()
			{
				QString s = activeWidget()->evalExpr( "&readonly" );
				bool _readWrite = (s[0] != '1');
				return _readWrite;
			}

			bool Document::closeURL()
			{
				if (!KParts::ReadWritePart::closeURL ())
					return false;
				activeWidget()->sendCmdLineCmd("confirm wa");
				activeWidget()->sendCmdLineCmd("bd!");
				emit fileNameChanged ();
				m_url = KURL();
				return true;
			}

			bool Document::openFile()
			{
				if (!m_readOnly)
					saveFile();
				activeWidget()->sendCmdLineCmd("call OpenFile(\"" + m_file + "\")" );
				if (m_readOnly) {
					setReadWrite(false);
				}
				return true;
			}

			bool Document::saveFile()
			{
				activeWidget()->sendCmdLineCmd("wa");
				return true;
			}

			void Document::copy()
			{
				((View*)activeView)->copy();
			}

			// EditInterface implementation
			QString Document::text() const
			{
				activeWidget()->sendNormalCmd("gg\"kyG''");
				return activeWidget()->evalExpr( "@k" );
			}

			QString Document::text( unsigned int startLine, unsigned int startCol,
					unsigned int endLine, unsigned int endCol ) const
			{
				QString text = QString::fromLatin1("Text(%1,%2,%3,%4)")
					.arg(startLine+1).arg(startCol+1).arg(endLine+1).arg(endCol+1);
				text = activeWidget()->evalExpr(text);
				return text;
			}

			QString Document::textLine( unsigned int line ) const
			{
				QString text = QString::fromLatin1("getline(%1)")
					.arg(line+1);
				text = activeWidget()->evalExpr(text);
				return text;
			}

			unsigned int Document::numLines() const
			{
				QString text = activeWidget()->evalExpr("line(\"$\")");
				return text.toUInt();
			}

			unsigned int Document::length() const
			{
				QString text = activeWidget()->evalExpr("line2byte(\"$\")");
				return text.toUInt();
			}

			int Document::lineLength( unsigned int line ) const
			{
				return textLine(line+1).length();
			}

			bool Document::setText( const QString &text )
			{
				kdDebug() << "Document :: setText" << text << endl;
				if (text.isNull() || text.isEmpty()) {
					clear();
					return true;
				}
				QString text2 (text);
				text2.setLength(text2.length()); //make a deep copy
				if (activeWidget()->useDCOP()) {
					text2 = text2.replace(QRegExp("\n"),"\\n");
					text2 = text2.replace(QRegExp("\""),"\\\"");
					QString cmd("call SetText(\"");
					cmd	+= text2; 
					cmd += "\")";
					activeWidget()->sendCmdLineCmd(cmd);
					return true;
				} else {
					clear();
					activeWidget()->sendInsertCmd(text2);
					return true;
				}
			}

			bool Document::clear()
			{
				kdDebug() << "Document :: clear" << endl;
				activeWidget()->sendCmdLineCmd("% delete");
				return true;
			}

			bool Document::insertText( unsigned int line, unsigned int col, const QString &text )
			{
//				kdDebug() << "insertText" << line << " " << col << " " << text << endl;
				QString text2 (text);
				text2 = text2.replace(QRegExp("\n"),"\\n");
				text2 = text2.replace(QRegExp("\""),"\\\"");
				if (line == numLines()) activeWidget()->sendNormalCmd("Go"); //add a new line
				line++;
				col++;
				QString cmd;
				cmd+="call Insert(";
				cmd+=QString::number(line);
				cmd+=",";
				cmd+=QString::number(col);
				cmd+=",\"";
				cmd+=text2;
				cmd+="\")";
				activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			bool Document::removeText( unsigned int startLine, unsigned int startCol,
					unsigned int endLine, unsigned int endCol )
			{
//				kdDebug() << "removeText" << startLine << " " << startCol << " " << endLine << " " << endCol << endl;
				QString cmd=QString("call Remove(%1,%2,%3,%4)").arg(startLine+1).arg(startCol+1).arg(endLine+1).arg(endCol+1);
				activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			bool Document::insertLine( unsigned int line, const QString &text )
			{
//				kdDebug() << "insertLine line : " << line << " text:" << text << endl;
				if (text.isEmpty() || text.isNull()) return true;
				QString cmd;
				QString text2 (text);
				text2 = text2.replace(QRegExp("\n"),"\\n");
				text2 = text2.replace(QRegExp("\""),"\\\"");
				cmd+="call InsertLine(";
				cmd+=QString::number(line+1);
				cmd+=",\"";
				cmd+=text2;
				cmd+="\")";
				activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			bool Document::removeLine(unsigned int line)
			{
				QString cmd=QString("%1 remove").arg(line+1);
				activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			// CursorInterface implementation
			KTextEditor::Cursor* Document::createCursor() {
				Cursor* cursor = new Cursor(this);
				_cursors.append(cursor);
				return cursor;
			}

			//UndoInterface -----------------------
			void Document::clearUndo () 
			{
				activeWidget()->sendCmdLineCmd("call ClearUndo()");
				emit undoChanged();	
			}
			
			void Document::clearRedo () 
			{
				//IMPOSSIBLE IN VIM
			}
			
			unsigned int Document::undoCount () const 
			{
				return undoSteps();				
			}
			
			unsigned int Document::redoCount () const 
			{
				//IMPOSSIBLE IN VIM
				return 0;
			}
			
			unsigned int Document::undoSteps () const 
			{
				QString levels = activeWidget()->evalExpr("&undolevels");
				return levels.toUInt();
			}
			
			void Document::setUndoSteps ( unsigned int steps ) 
			{
  			activeWidget()->setVimVariable("undolevels", QString::number(steps));
				emit undoChanged();	
			}
			
			void Document::undo () 
			{
				activeWidget()->sendNormalCmd("u");
			}
			
			void Document::redo () 
			{
				activeWidget()->sendNormalCmd("<CTRL-R>");
			}

			//SelectionInterface ---------------
			bool Document::setSelection ( 
					unsigned int startLine, 
					unsigned int startCol, 
					unsigned int endLine, 
					unsigned int endCol )
			{
				QString cmd=QString("call SetSelection(%1,%2,%3,%4)").arg(startLine+1).arg(startCol+1).arg(endLine+1).arg(endCol+1);
				activeWidget()->sendCmdLineCmd(cmd);
				return true;
			}

			bool Document::clearSelection ()
			{
				activeWidget()->sendNormalCmd("");
				return true;
			}

			bool Document::hasSelection () const
			{
				QString mode = activeWidget()->evalExpr("mode()");
				if (mode == "v" || mode == "V" || mode == "CTRL-V" // visual mode
						|| mode == "s" || mode == "S" || mode == "CTRL-S")  //selection mode
					return true; //we consider that being in visual mode _is_ having a selection
				return false;
			}

			QString Document::selection () const
			{
				activeWidget()->sendNormalCmd("\"ky"); //copy in register k
				return activeWidget()->evalExpr( "@k" ); //return the value
			}

			bool Document::removeSelectedText ()
			{
				activeWidget()->sendRawCmd("x");
				return true;
			}

			bool Document::selectAll()
			{
				activeWidget()->sendNormalCmd("gggH\x0FG");
				return true;
			}

			// SearchInterface -------------------------
			bool Document::searchText (unsigned int startLine, unsigned int startCol,
					const QString &text, unsigned int *foundAtLine, unsigned int *foundAtCol,
					unsigned int *matchLen, bool casesensitive, bool backwards)
			{
				QString text2 (text);
				if (!casesensitive) {
					text2.prepend("\\c");	
					text2.append("\\c");
				} else {
					text2.prepend("\\C");	
					text2.append("\\C");
				}
				return searchText(startLine, startCol, QRegExp(text), foundAtLine, foundAtCol, matchLen, backwards);
			}

			bool Document::searchText (unsigned int /*startLine*/, unsigned int /*startCol*/,
					const QRegExp &/*regexp*/, unsigned int * /*foundAtLine*/, unsigned int * /*foundAtCol*/,
					unsigned int * /*matchLen*/, bool /*backwards*/)
			{
				//TODO
				return true;
			}

			// WordWrapInterface -------------------------
			void Document::setWordWrap (bool ww) {
				if (ww)
	  			activeWidget()->setVimVariable("textwidth", QString::number(78));
				else
	  			activeWidget()->setVimVariable("textwidth", QString::number(0));
			}

			bool Document:: wordWrap () {
				QString ww = activeWidget()->evalExpr("&textwidth");
				if (ww.toUInt()>0) return true;
				else return false;
			}

			void Document::setWordWrapAt (unsigned int ww ) {
  			activeWidget()->setVimVariable("textwidth", QString::number(ww));
			}

			unsigned int Document::wordWrapAt () {
				QString ww = activeWidget()->evalExpr("&textwidth");
				return ww.toUInt();
			}

			// EncodingInterface -------------------------
			// MBYTE feature required
			void Document::setEncoding (const class QString &e) {
				if (!e.isNull())
					activeWidget()->setVimVariable("fileencoding", e);
			}

			QString Document::encoding() const {
				return activeWidget()->evalExpr("&fileencoding");
			}
			
			// HighlightingInterface -------------------------
			unsigned int Document::hlMode () 
			{
				return 0;
			}

			bool Document::setHlMode (unsigned int /*mode*/) 
			{
				return false;
			}

			unsigned int Document::hlModeCount () 
			{
				return 0;
			}

			QString Document::hlModeName (unsigned int /*mode*/) 
			{
				return QString();
			}

			QString Document::hlModeSectionName (unsigned int /*mode*/) 
			{
				return QString();
			}

			void Document::keyboardEvent(QCString string, int col, int row) 
			{
				kdDebug() << "*** *** *** DCOP received keyboardEvent " << string << " at " 
						<< col << "," << row << endl;
				emit textChanged();
				emit charactersInteractivelyInserted(row,col,string);
				((View*)activeView)->emitCursorPositionChanged();
			}
			
			void Document::mousePEvent(int /*button*/, int /*modifiers*/, int /*col*/, int /*row*/) 
			{
				kdDebug() << "*** *** *** DCOP received mousePEvent " << endl;
				((View*)activeView)->emitCursorPositionChanged();
			}
			
			void Document::mouseWhlEvent(int /*button*/, int /*modifiers*/, int /*col*/, int /*row*/)
			{
				kdDebug() << "*** *** *** DCOP received mouseWhlEvent " << endl;
				((View*)activeView)->emitCursorPositionChanged();
			}
			
			void Document::mouseDblClickEvent(int /*button*/, int /*modifiers*/, int /*col*/, int /*row*/)
			{
				kdDebug() << "*** *** *** DCOP received mouseDblClickEvent " << endl;
				((View*)activeView)->emitCursorPositionChanged();
			}

}

#include "vimdocument.moc"

