/****************************************************************************
**
** Copyright (C) 2001-2006 Frank Hemer <frank@hemer.org>,
**                         Tilo Riemer <riemer@crossvc.com>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"

#include <stdio.h>
#include <iostream>
#include <stdlib.h>

#include <qapplication.h>
#include <qregexp.h>
#include <qtimer.h>

#include "globals.h"
#include "InteractiveCmdThread.h"
#include "TextDecoder.h"


//------------------------------------------------------------------------------------

InteractiveCmdThread::InteractiveCmdThread (QWidget *qw, CvsBuffer *pCvsBuffer, CCvsOutput *pMessages,
      QDir &workDir, QString cmdstr, int cmd,
      QString module, CommandInterface *inst)
   : QObject(),
     m_qw(qw),
     m_pCvsBuffer(pCvsBuffer),
     m_pMessages(pMessages),
     m_workDir(workDir),
     m_cmdStr(cmdstr),
     m_cmd(cmd),
     m_module(module),
     instance(inst)
{
   m_pEnvironment = NULL;
   m_pTmpFile = NULL;
   //   m_pMessages->insert("cmd: "+m_cmdStr+" "+m_module+"\n\n");
   m_pCommandList = new QStringList();
   *m_pCommandList = QStringList::split(" && ",m_cmdStr);
}

//------------------------------------------------------------------------------------

InteractiveCmdThread::~InteractiveCmdThread () {
   disconnect(this,0,0,0);
   delete m_pCommandList;
   delete m_pEnvironment;
   delete m_pTmpFile;
   // delete m_proc;  m_proc is a child of this object and should be deleted automatically
}

//------------------------------------------------------------------------------------

bool InteractiveCmdThread::start() {
	
	//read environment settings
	//make sure this is only called once and kept till end of thread,
	//in case more than one call is executed (concatenated with && )
	//and the following commands might need these env too!
	delete m_pEnvironment;
	m_pEnvironment = new QStringList();
	
	m_pEnvironment->append("CVS_PASSFILE="+CVSPASSPATH);
	
#ifdef Q_WS_MAC
	QString askpass = APPDIR + "/" + LC_TOOLS_DIR + "/ssh-askpass.app/Contents/MacOS/ssh-askpass";
#else
	QString askpass = APPDIR + "/" + LC_TOOLS_DIR + "/ssh-askpass.bin";
#endif
	if (QFileInfo(askpass).exists()) {
		m_pEnvironment->append("SSH_ASKPASS="+askpass);
	} else {
		addToEnv("SSH_ASKPASS");
	}
	
#ifdef Q_WS_MAC
	if (!addToEnv("DISPLAY")) m_pEnvironment->append("DISPLAY=:0.0");
#else
	addToEnv("DISPLAY");
#endif
	
	//  Have the process inherit the PATH setting so that ssh and other 
	//  commands can be found
	addToEnv("PATH");
	addToEnv("HOME");
	
	return run();
}

//-------------------------------------------------------------------------------------

bool InteractiveCmdThread::run () {

   delete m_pTmpFile;
   m_pTmpFile = NULL;

   //reset ouput line offset
   m_bIsOutputLineOffsetChecked = false;
   outputLineOffset = 0;

   //parse for exports and append to environment
   bool found;
   do {
      found = false;
      m_command = m_pCommandList->first().stripWhiteSpace();
      m_pCommandList->pop_front();
      if (m_command.find("export ")==0) {
#ifdef Q_WS_WIN
         m_pEnvironment->append(adaptPath(m_command.mid(7).stripWhiteSpace()));
#else
         m_pEnvironment->append(m_command.mid(7).stripWhiteSpace());
#endif
         found = true;
      }
   } while (found);
   if (m_pEnvironment->empty()) {
      delete(m_pEnvironment);
      m_pEnvironment = NULL;
   }

   //check for writing into tmp files
   int pos;
   if ((pos = m_command.find(" > "))>-1) {

      QString fileName = m_command.mid(pos+3).stripWhiteSpace();
      m_command = m_command.left(pos).stripWhiteSpace();
      m_pTmpFile = new QFile(fileName);
      if(!m_pTmpFile->open(IO_WriteOnly | IO_Append)) {
         delete(m_pTmpFile);
         m_pTmpFile = NULL;
      }
   } else {
      m_pTmpFile = NULL;
   }

   //complete the command
   m_command += " " + m_module;

   //write cmd to message window
   QString commandWithoutPlaceholder = m_command;
   commandWithoutPlaceholder.replace(PLACEHOLDER_FOR_QUOTATION_MARKS, "\"");
   m_pCvsBuffer->append("cmd: "+commandWithoutPlaceholder+getSystemLF());
   m_pMessages->insert("cmd: "+commandWithoutPlaceholder+getSystemLF());

   //split command options and prevent doublequoted strings from being split
   //the doublequotes are removed though
   if (m_command.startsWith("\"")) {
      pos = 0;
   } else {
      pos = 1;
   }
   QStringList commandMasqued = QStringList::split("\"",m_command);
   QStringList commandArgs;
   QStringList::Iterator outer = commandMasqued.begin();
   while (outer != commandMasqued.end()) {
      if (pos % 2) {
         QStringList tmpList = QStringList::split(" ",(*outer));
         for ( QStringList::Iterator inner = tmpList.begin(); inner != tmpList.end(); ++inner ) {
            commandArgs.append((*inner).replace(PLACEHOLDER_FOR_QUOTATION_MARKS, "\""));
         }
      } else {
         commandArgs.append((*outer).replace(PLACEHOLDER_FOR_QUOTATION_MARKS, "\""));
      }
      ++pos;
      ++outer;
   }

   //   m_pMessages->insert("*******start********\n");
   //   for ( QStringList::Iterator it = commandArgs.begin(); it != commandArgs.end(); ++it ) {
   //     m_pMessages->insert(*it+"\n");
   //   }
   //   m_pMessages->insert("*******end********\n");

   //setup QProcess
   m_proc = new QProcess(this);

   m_proc->setCommunication(QProcess::Stdin|QProcess::Stdout|QProcess::Stderr|QProcess::DupStderr);

   m_proc->setWorkingDirectory(m_workDir);
   m_proc->setArguments(commandArgs);

   connect( m_proc, SIGNAL(readyReadStdout()), this, SLOT(readFromStdout()) );
   connect( m_proc, SIGNAL(processExited()), this, SLOT(ready()) );

   //and run it
   if ( !m_proc->start(m_pEnvironment) ) {
      // error handling
      std::cout << "can't start qprocess\n";
      m_pMessages->insert("\nLinCVS ERROR: can't start qprocess\n\nMaybe cvs is not in $PATH?\n");
      m_pCommandList->clear();
      ready();
      return FALSE;
   } else {
      return TRUE;
   }
}

bool InteractiveCmdThread::addToEnv(QString var) {

   if (m_pEnvironment) {
      QString tmpEnv = getenv(var);
      if (tmpEnv.length() != 0) {
	 m_pEnvironment->append(var+"="+tmpEnv);
         return TRUE;
      }
   }
   return FALSE;
}

void InteractiveCmdThread::ready() {

   if (m_pTmpFile) {
      m_pTmpFile->flush();
      m_pTmpFile->close();
   }

   if (m_pCommandList->empty()) {
      m_pMessages->insert(getSystemLF());
      emit cvsCommandFinished();
   } else {
      run();
   }
}

void InteractiveCmdThread::readFromStdout() {

   while (true) {
      QString line;
      QByteArray nextLine;
      do {
	 nextLine = m_proc->readStdout();
	 if (nextLine.size() == 0) {
	    if (line.isEmpty()) {
	       return;
	    } else {
	       break;
	    }
	 }
	 //test for proxy startup message, if found, ignore it!
	 if (!m_bIsOutputLineOffsetChecked) {
	    QByteArray tmp = nextLine.copy();
	    int size = tmp.size();
	    tmp.resize(size+1);
	    tmp[size] = '\0';
	    QString firstLine(tmp);
	    int start;
	    if ( (start = firstLine.find("(HTTP tunneling through ")) >= 0) {
	       start = firstLine.find("\n",start);
	       line = firstLine.left(start+1);
	       QByteArray hlp(nextLine.size()-start-1);
	       unsigned int i, j;
	       for (i=0,j=start+1;j<nextLine.size();i++,j++) {
		  hlp.at(i)=nextLine.at(j);
	       }
	       nextLine = hlp;
	       outputLineOffset = 1;
	    }
	    m_bIsOutputLineOffsetChecked = true;
	 }
	 if (m_pTmpFile) {//write to tmp file
	    int size = nextLine.size();
	    do {
	       int written = m_pTmpFile->writeBlock(nextLine);
	       size -= written;
	       if (size>0) {
		  int pos,fill;
		  for (pos = 0,fill = written; written < size; pos++,fill++) {
		     nextLine[pos]=nextLine[fill];
		  }
		  nextLine.truncate(pos+1);
	       }
	    } while (size>0);
	 } else {//write to cvs output buffer
	    line.append(I18n::g_pTextDecoder->decode(nextLine.data(), nextLine.size()));
	    //old version
	    /*
	      int size = nextLine.size();
	      nextLine.resize(size+1);
	      nextLine[size] = '\0';
	      line.append(nextLine);
	    */
	 }
      } while (true);
    
      if (!checkForInputRequest(&line)) {
	 if (!line.isEmpty()) {
	    m_pCvsBuffer->append(line);
	    m_pMessages->insert(line);//use insert because append adds additional '\n'
	 }
      }
   }
}

//-------------------------------------------------------------------------------------------------

bool InteractiveCmdThread::checkForInputRequest(QString *line) {
   switch( m_cmd) {
      case CVS_RELEASE_CMD: {
	 return release(line);
	 break;
      }
      case CVS_RELEASE_D_CMD: {
	 return release(line);
	 break;
      }
      case CVS_UNEDIT_CMD: {
	 return unedit(line);
	 break;
      }
      case CVS_NOT_INTERACTIVE_CMD: {
	 // nothing done
      }
      default: { // additional not listed non-interactive commands, that need no implementation here but in afterCall
      }
   }
   return false;
}

//-------------------------------------------------------------------------------------------------

bool InteractiveCmdThread::release(QString *line) {
   int a = 0;
   int b = 0;
   QString msg;

   a = line->find( ':',-4);
   b = line->findRev( "revert changes?");
   if (a>=0 || b>=0) {
      if (a>=0) { // last two lines
	 int i =  line->findRev('\n',a-line->length()-1);
	 i = line->findRev('\n',i-line->length()-1) + 1;
	 msg = line->mid(i);
	 m_pCvsBuffer->append(line->left(i));
	 m_pMessages->insert(line->left(i));
      }
      else msg = *line;// only one line
      MESSAGE message;
      message.type = CVS_Y_N;
      message.request = msg;
      message.reply = "";
      emit requestReceived(message);
      return true;
   }
   return false;
}

//-------------------------------------------------------------------------------------------------

bool InteractiveCmdThread::unedit(QString *line) {

   int llen;
   if ( (llen = (int)line->length()-19) >= 0) {
      if ( line->findRev("revert changes?") >= llen) {
	 MESSAGE message;
	 message.type = CVS_Y_N;
	 message.request = *line;
	 message.reply = "";
	 emit requestReceived(message);
	 return true;
      }
   }
   return false;
}

//-------------------------------------------------------------------------------------------------

void InteractiveCmdThread::writeToShell( QString *line, QString s) {
   m_proc->writeToStdin(s);
   if (!line->isEmpty()) {
      m_pCvsBuffer->append( *line+s);
      m_pMessages->insert( *line+s);
   } else {
      m_pCvsBuffer->append( s);
      m_pMessages->insert( s);
   }
}

//-------------------------------------------------------------------------------------------------

bool InteractiveCmdThread::sendReply(MESSAGE msg) {
   writeToShell(&msg.request,msg.reply);
   return true;
}

//-------------------------------------------------------------------------------------------------

CommandInterface* InteractiveCmdThread::getInstance() {
   return instance;
}

//-------------------------------------------------------------------------------------------------

int InteractiveCmdThread::getCommand() {
   return m_cmd;
}

//-------------------------------------------------------------------------------------------------

void InteractiveCmdThread::exit() {
   m_proc->tryTerminate();
   m_pMessages->insert("Please stand by, process will be terminated\n");
   QTimer::singleShot( 5000, m_proc, SLOT( kill()) );
}

