/* Copyright (C) 2002 Philippe Fremy <pfremy@kde.org>
   Mickael Marchand <marchand@kde.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.

   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 <ktempfile.h>
#include <kstandarddirs.h>
#include <kapplication.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kconfig.h>
#include <dcopclient.h>
#include <klocale.h>

#include <qfileinfo.h>
#include <qregexp.h>
#include <X11/Xlib.h>
#include <kwinmodule.h>
#include <kwin.h>
#include <unistd.h>
#include <iostream>
#include "vimwidget.h"

using namespace std;

VimWidget::VimWidget( QWidget * parent, const char * name, WFlags f )
  : QXEmbed( parent, name, f )
{
	setFocusPolicy(StrongFocus);
	dcopcmd.setAutoDelete(true);
	_dying = false;
  _vimReady = false;
  hideTool=true;
  hideMenu=true;
	usedcop=true;

  abort = !setExecutable();
  if (abort == false) {
    _serverName = KApplication::randomString(10).upper();

    kwm = new KWinModule(this);
    connect( kwm, SIGNAL(windowAdded(WId)), this, SLOT(embedVimWid(WId)) );
    kwm->doNotManage(_serverName);

    KProcess _vimProcess;

    QString n = ":set titlestring=";
    n+=_serverName;

    QString kvimscript = locate("data", "vimpart/kvim.vim");
    kdDebug(90000) << "kvimscript = " << kvimscript << endl;

    _vimProcess << _vimExecutable << "-g" /*<< "-geometry" << "1000x1000" */<< "--cmd" << n << "-c" << n << "--servername" << _serverName << "--cmd" << "source " + kvimscript;
    if (hideMenu)
      _vimProcess << "--cmd" << ":set guioptions-=m" << "-c" << ":set guioptions-=m";
    if (hideTool)
      _vimProcess << "--cmd" << ":set guioptions-=T" << "-c" << ":set guioptions-=T";
    if ( guitype == 1 ) _vimProcess << "--caption" << _serverName << "-notip";
    _vimProcess.start(KProcess::Block);
  }
}

bool VimWidget::setExecutable()
{
  QString vimExecutable;
  KConfig *config = new KConfig( "vimpartrc" );
	if (config->readBoolEntry("ready",false)==false) {
    KMessageBox::sorry( this, i18n("Please use the KDE control module and configure the Vim component."), i18n("Vim Error") );
    delete config;
    return false;
	}
  vimExecutable = config->readPathEntry("executable");
  hideTool = !config->readBoolEntry("tool",false);
  hideMenu = !config->readBoolEntry("menu",false);
  usedcop = config->readBoolEntry("usedcop",false);
  guitype = config->readNumEntry("guin",-1);
  if ( guitype == -1 ) {
    KMessageBox::sorry( this, i18n("Please use the KDE control module and configure the Vim component."), i18n("Vim Error") );
    delete config;
    return false;
  }

  QString msg = i18n("\nUnable to start the Vim editing control. Use the KDE Control Center to reconfigure and test your Vim component.\n");

  if (vimExecutable.isEmpty() ) {
    KMessageBox::sorry( this, i18n("Please use the KDE control module and configure the Vim component.\n") + msg,
                        i18n("Vim Error") );
    return false;
  }

  QFileInfo fi( vimExecutable );

  if (fi.exists() == false) {
    KMessageBox::sorry( this, i18n("Vim executable '%1' does not exist or is not accessible.\n").arg( vimExecutable ) + msg,
                        i18n("Vim Error") );
    delete config;
    return false;
  }

  if (fi.isExecutable() == false) {
    KMessageBox::sorry( this, i18n("Vim executable '%1' is not executable.\n").arg( vimExecutable ) + msg,
                        i18n("Vim Error") );
    delete config;
    return false;
  }

  _vimExecutable = vimExecutable;
  delete config;
  return true;
}

bool VimWidget::close( bool alsoDelete )
{
  closeVim();
  return QXEmbed::close( alsoDelete );
}

void VimWidget::closeVim()
{
	if (_dying || !_vimReady) return;
	_dying = true;
	kdDebug(90000) << "closeVim()" << endl;

	if (!usedcop) {
  	while (!_pendingCmd.isEmpty())
	  	processX11Cmd(); //clean up
		int code;
		XVim xvim;
		char *res=xvim.sendToVim(qt_xdisplay(),_serverName.latin1(), "<C-\\><C-N>:call ForceQuit()<C-M>", 1,&code);
		if (res!=NULL && code) {
			kdDebug(90000) << "error " << code << endl;
			kdDebug(90000) << "result : " << res << endl;
		}
		res=xvim.sendToVim(qt_xdisplay(),_serverName.latin1(), "<C-\\><C-N>:call ForceQuit()<C-M>", 1,&code);
		if (res!=NULL && code) {
			kdDebug(90000) << "error " << code << endl;
			kdDebug(90000) << "result : " << res << endl;
		}
	} else {
		while (!dcopcmd.isEmpty()) processDcopCmd();
		QByteArray data;
		QDataStream arg(data, IO_WriteOnly);
		arg << QString("call ForceQuit()");
		if( !kapp->dcopClient()->send(QCString(_serverName.latin1()), "KVim", "execCmd(QString)", data))
			kdDebug(90000) << "problem while quitting through DCOP" << endl;
	}
}

VimWidget::~VimWidget()
{
	kdDebug(90000) << "VimWidget destructor" << endl;
  closeVim();
}

void VimWidget::embedVimWid( WId wid )
{
  KWin::Info info = KWin::info(wid);
	kdDebug(90000) << "starting embedding " << wid << " " << info.name << endl;

	if (_vimReady) return; //already embedded ;)
  if (info.name != _serverName ) return;

	disconnect( kwm, SIGNAL(windowAdded(WId)), this, SLOT(embedVimWid(WId)) );
	if (guitype != 1) {
		kdDebug(90000) << "setting XPLAIN mode for QXEmbed" << endl;
		setProtocol(QXEmbed::XPLAIN);
	}
	embed(wid);
	_vimReady=true;
	emit vimReady();
  kdDebug(90000)<<"Vim window ready" << endl;
	if (usedcop) processDcopCmd();
	else processX11Cmd();
}

void VimWidget::sendNormalCmd( const QString & cmd )
{
	if (usedcop)
		processDcopCmd(cmd, Normal);
	else
	  sendRawCmd( "<C-\\><C-N>" + cmd );
}

void VimWidget::sendInsertCmd( const QString & cmd)
{
	if (usedcop) {
		processDcopCmd (cmd,Insert);
	} else {
	  QString s = cmd;
   	s += "<C-\\><C-N>";
	  sendRawCmd( "<C-\\><C-N>i" + s);
	}
}

void VimWidget::sendCmdLineCmd( const QString & cmd )
{
	if (usedcop) {
		processDcopCmd (cmd, Cmd);
	} else
		sendRawCmd( "<C-\\><C-N>:" + cmd + "<C-M>" );
}

void VimWidget::sendRawCmd( const QString & cmd )
{
	if (usedcop) {
		processDcopCmd (cmd, Raw);
	} else {
	  processX11Cmd( cmd );
	}
}

// DCOP action queue execution
void VimWidget::processDcopCmd (QString cmd, VimDcop mode)
{
	if (!cmd.isEmpty()) {
		dcopcmd.append(new DCmd(cmd,mode));
	}
  if (_vimReady == true && dcopcmd.isEmpty() == false) {
		DCmd *dcmd = dcopcmd.first();
		QByteArray data,rdata;
		QDataStream arg(data, IO_WriteOnly);
		arg << dcmd->getCmd();
		QCString runmode,rtype;
		switch (dcmd->getMode()) {
			case Insert:
				runmode = "execInsert(QString)";
				break;
			case Normal:
				runmode = "execNormal(QString)";
				break;
			case Cmd:
				runmode = "execCmd(QString)";
				break;
			case Raw:
			default:
				runmode = "execRaw(QString)";
				break;
		}
		kdDebug(90000) << "Sending command: " << dcmd->getCmd() << " through DCOP with exec function : "
			<< runmode << " for mode : " << dcmd->getMode() <<  endl;
		if( !kapp->dcopClient()->call(QCString(_serverName.latin1()), "KVim", runmode, data, rtype, rdata, true ))
			kdDebug(90000) << "problem while sending through DCOP" << endl;
		else dcopcmd.removeFirst();
	}
  if (_vimReady == true && dcopcmd.isEmpty() == false) processDcopCmd();
}

// X11 action queue execution
void VimWidget::processX11Cmd( QString cmd )
{
  if (cmd.isEmpty() == false) {
    _pendingCmd << cmd;
  }

  if (_vimReady == true && _pendingCmd.isEmpty() == false) {
    QStringList::iterator it = _pendingCmd.begin();
    QString s = (*it);
    kdDebug(90000) << "sending through xvim : " << s << endl;
		int code;
		XVim xvim;
		char *res;
		res=xvim.sendToVim(qt_xdisplay(),_serverName.latin1(), s.latin1(), 1,&code);
		if (code==-1) {
			kdDebug(90000) << "error " << code << endl;
		} else _pendingCmd.remove( it );
		processX11Cmd();
  }
}

QString VimWidget::evalExpr( const QString &expr )
{
	//make sure, _vimReady is true and flush all queues before doing synchronous calls (eval expr)
	//we should really provide async/sync functions ...//XXX
	//if _vimReady is false, then we must wait...

	kdDebug(90000) << "evalExpr " << expr << endl;
	//this one is _really_ problematic
//	while (!_vimReady)
		//kapp->processOneEvent();//don't freeze
	if (!_vimReady) kdDebug(90000) << "NOT READY (means app not embedded yet, but results should be okay)" << endl;
	if (usedcop) {
		processDcopCmd(); //flush the queue
		return DcopEvalExpr(expr); //now do it
	} else {
		processX11Cmd();
		return X11EvalExpr(expr); //now do it
	}
}

QString VimWidget::DcopEvalExpr( const QString &expr)
{
	kdDebug(90000) << "DcopEvalExpr " << expr << endl;
	QByteArray data,rdata;
	QDataStream arg(data, IO_WriteOnly);
	arg << expr;
	QCString rtype;

	if( !kapp->dcopClient()->call(QCString(_serverName.latin1()), "KVim", "eval(QString)", data, rtype, rdata, true )) {
		kdDebug(90000) << "problem while sending through DCOP" << endl;
		return QString();
	}

  QDataStream reply(rdata, IO_ReadOnly);
	if (rtype=="QString") {
		QString result;
		reply >> result;
		kdDebug(90000) << "DCOP eval expr result: " << result << endl;
		return result;
	}
	return QString();
}

QString VimWidget::X11EvalExpr( const QString &expr )
{
	kdDebug(90000) << "X11EvalExpr " << expr << endl;
	int code;
	XVim xvim;
	char *res=xvim.sendToVim(qt_xdisplay(),_serverName.latin1(), expr.latin1(), 0,&code);
	if (res!=NULL && code) {
		kdDebug(90000) << "expr error " << code << endl;
		kdDebug(90000) << "expr error message : " << res << endl;
	}
	if (res!=NULL) {
		QString result(res);
		kdDebug(90000) << "result :" << result << endl;
		return result;
	} else
		return QString();
}

#include "vimwidget.moc"
