/*
    This file is part of Konsole, an X terminal.
    Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.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.

    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; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA.
*/
      
#include "session.h"
#include "zmodem_dialog.h"

#include <kdebug.h>
#include <dcopclient.h>
#include <tdemessagebox.h>
#include <knotifyclient.h>
#include <tdelocale.h>
#include <tdeprocio.h>
#include <krun.h>
#include <kshell.h>
#include <tdestandarddirs.h>

#include <stdlib.h>
#include <tqfile.h>
#include <tqdir.h>
#include <tqregexp.h>
#include <tqtextedit.h>

/*! \class TESession

    Sessions are combinations of TEPTy and Emulations.

    The stuff in here does not belong to the terminal emulation framework,
    but to main.cpp. It serves it's duty by providing a single reference
    to TEPTy/Emulation pairs. In fact, it is only there to demonstrate one
    of the abilities of the framework - multible sessions.
*/

TESession::TESession(TEWidget* _te, const TQString &_term, ulong _winId, const TQString &_sessionId, const TQString &_initial_cwd)
   : DCOPObject( _sessionId.latin1() )
   , sh(0)
   , connected(true)
   , monitorActivity(false)
   , monitorSilence(false)
   , notifiedActivity(false)
   , masterMode(false)
   , autoClose(true)
   , wantedClose(false)
   , schema_no(0)
   , font_no(3)
   , silence_seconds(10)
   , add_to_utmp(true)
   , xon_xoff(false)
   , pgm(TQString())
   , args(TQStrList())
   , sessionId(_sessionId)
   , cwd("")
   , initial_cwd(_initial_cwd)
   , zmodemBusy(false)
   , zmodemProc(0)
   , zmodemProgress(0)
   , encoding_no(0)
{
  //kdDebug(1211)<<"TESession ctor() new TEPty"<<endl;
  te = _te;
  //kdDebug(1211)<<"TESession ctor() new TEmuVt102"<<endl;
  em = new TEmuVt102(te);
  font_h = te-> fontHeight();
  font_w = te-> fontWidth();
  TQObject::connect(te,TQ_SIGNAL(changedContentSizeSignal(int,int)),
                   this,TQ_SLOT(onContentSizeChange(int,int)));
  TQObject::connect(te,TQ_SIGNAL(changedFontMetricSignal(int,int)),
                   this,TQ_SLOT(onFontMetricChange(int,int)));

  term = _term;
  winId = _winId;
  iconName = "konsole";

  setPty( new TEPty() );

  connect( em, TQ_SIGNAL( changeTitle( int, const TQString & ) ),
           this, TQ_SLOT( setUserTitle( int, const TQString & ) ) );
  connect( em, TQ_SIGNAL( notifySessionState(int) ),
           this, TQ_SLOT( notifySessionState(int) ) );
  monitorTimer = new TQTimer(this);
  connect(monitorTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(monitorTimerDone()));

  connect( em, TQ_SIGNAL( zmodemDetected() ), this, TQ_SLOT(slotZModemDetected()));

  connect( em, TQ_SIGNAL( changeTabTextColor( int ) ),
           this, TQ_SLOT( changeTabTextColor( int ) ) );

  //kdDebug(1211)<<"TESession ctor() done"<<endl;
}

void TESession::setPty(TEPty *_sh)
{
  if ( sh ) {
    delete sh;
  }
  sh = _sh;
  connect( sh, TQ_SIGNAL( forkedChild() ),
           this, TQ_SIGNAL( forkedChild() ));

  //kdDebug(1211)<<"TESession ctor() sh->setSize()"<<endl;
  sh->setSize(te->Lines(),te->Columns()); // not absolutely nessesary
  sh->useUtf8(em->utf8());
  //kdDebug(1211)<<"TESession ctor() connecting"<<endl;
  connect( sh,TQ_SIGNAL(block_in(const char*,int)),this,TQ_SLOT(onRcvBlock(const char*,int)) );

  connect( em,TQ_SIGNAL(sndBlock(const char*,int)),sh,TQ_SLOT(send_bytes(const char*,int)) );
  connect( em,TQ_SIGNAL(lockPty(bool)),sh,TQ_SLOT(lockPty(bool)) );
  connect( em,TQ_SIGNAL(useUtf8(bool)),sh,TQ_SLOT(useUtf8(bool)) );

  connect( sh,TQ_SIGNAL(done(int)), this,TQ_SLOT(done(int)) );

  if (!sh->error().isEmpty())
     TQTimer::singleShot(0, this, TQ_SLOT(ptyError()));
}

void TESession::ptyError()
{
  // FIXME:  sh->error() is always empty
  if ( sh->error().isEmpty() )
    KMessageBox::error( te->topLevelWidget(),
       i18n("Konsole is unable to open a PTY (pseudo teletype).  It is likely that this is due to an incorrect configuration of the PTY devices.  Konsole needs to have read/write access to the PTY devices."), 
       i18n("A Fatal Error Has Occurred") );
  else
    KMessageBox::error(te->topLevelWidget(), sh->error());
  emit done(this);
}

void TESession::changeWidget(TEWidget* w)
{
  TQObject::disconnect(te,TQ_SIGNAL(changedContentSizeSignal(int,int)),
                     this,TQ_SLOT(onContentSizeChange(int,int)));
  TQObject::disconnect(te,TQ_SIGNAL(changedFontMetricSignal(int,int)),
                     this,TQ_SLOT(onFontMetricChange(int,int)));
  te=w;
  em->changeGUI(w);
  font_h = te->fontHeight();
  font_w = te->fontWidth();
  sh->setSize(te->Lines(),te->Columns()); // not absolutely nessesary

  te->setDefaultBackColor(modifiedBackground);

  TQObject::connect(te,TQ_SIGNAL(changedContentSizeSignal(int,int)),
                   this,TQ_SLOT(onContentSizeChange(int,int)));
  TQObject::connect(te,TQ_SIGNAL(changedFontMetricSignal(int,int)),
                   this,TQ_SLOT(onFontMetricChange(int,int)));
}

void TESession::setProgram( const TQString &_pgm, const TQStrList &_args )
{
    pgm = _pgm;
    args = _args;
}

void TESession::run()
{
  // Upon a KPty error, there is no description on what that error was...
  // Check to see if the given program is executable.
  TQString exec = TQFile::encodeName(pgm);
  exec = KRun::binaryName(exec, false);
  exec = KShell::tildeExpand(exec);
  TQString pexec = TDEGlobal::dirs()->findExe(exec);
  if ( pexec.isEmpty() ) {
    kdError()<<"can not execute "<<exec<<endl;
    TQTimer::singleShot(1, this, TQ_SLOT(done()));
    return;
  }

  TQString appId=tdeApp->dcopClient()->appId();

  TQString cwd_save = TQDir::currentDirPath();
  if (!initial_cwd.isEmpty())
     TQDir::setCurrent(initial_cwd);
  sh->setXonXoff(xon_xoff);

  int result = sh->run(TQFile::encodeName(pgm), args, term.latin1(), 
          winId, add_to_utmp,
          ("DCOPRef("+appId+",konsole)").latin1(),
          ("DCOPRef("+appId+","+sessionId+")").latin1());
  if (result < 0) {     // Error in opening pseudo teletype
    kdWarning()<<"Unable to open a pseudo teletype!"<<endl;
    TQTimer::singleShot(0, this, TQ_SLOT(ptyError()));
  }
  sh->setErase(em->getErase());

  if (!initial_cwd.isEmpty())
     TQDir::setCurrent(cwd_save);
  else
     initial_cwd=cwd_save;

  sh->setWriteable(false);  // We are reachable via kwrited.
}

void TESession::changeTabTextColor( int color )
{
    emit changeTabTextColor( this, color );
}

void TESession::setUserTitle( int what, const TQString &caption )
{
    // (btw: what=0 changes title and icon, what=1 only icon, what=2 only title
    if (what == 0 || what == 2)
       userTitle = caption;
    if (what == 0 || what == 1)
       iconText = caption;
    if (what == 11) {
      TQString colorString = caption.section(';',0,0);
      TQColor backColor = TQColor(colorString);
      if (backColor.isValid()){// change color via \033]11;Color\007
        if (backColor != modifiedBackground) {
          modifiedBackground = backColor;
          te->setDefaultBackColor(backColor);
        }
      }
    }
    // 30/31/32 are OSC extensions that are not part of the official XTerm documentation
    if (what == 30)   // rename session via \033]30;<new name>\007
       renameSession(caption);
    if (what == 31) { // open specific folder via \033]31;<folder name>\007
       cwd=caption;
       cwd=cwd.replace( TQRegExp("^~"), TQDir::homeDirPath() );
       emit openURLRequest(cwd);
    }    
    if (what == 32) { // change icon via \033]32;Icon\007
       iconName = caption;
       te->update();
    }

    emit updateTitle(this);
}

TQString TESession::fullTitle() const
{
    TQString res = title;
    if ( !userTitle.isEmpty() )
        res = userTitle + " - " + res;
    return res;
}

void TESession::monitorTimerDone()
{
  if (monitorSilence) {
    KNotifyClient::event(winId, "Silence", i18n("Silence in session '%1'").arg(title));
    emit notifySessionState(this,NOTIFYSILENCE);
  }
  notifiedActivity=false;
}

void TESession::notifySessionState(int state)
{
  if (state==NOTIFYBELL) {
    te->Bell(em->isConnected(),i18n("Bell in session '%1'").arg(title));
  } else if (state==NOTIFYACTIVITY) {
    if (monitorSilence) {
      monitorTimer->start(silence_seconds*1000,true);
    }
    if (!monitorActivity)
      return;
    if (!notifiedActivity) {
      KNotifyClient::event(winId, "Activity", i18n("Activity in session '%1'").arg(title));
      notifiedActivity=true;
      monitorTimer->start(silence_seconds*1000,true);
    }
  }

  emit notifySessionState(this, state);
}

void TESession::onContentSizeChange(int height, int width)
{
  // ensure that image is at least one line high by one column wide
  const int columns = TQMAX( width/font_w , 1 );
  const int lines = TQMAX( height/font_h , 1 );

  em->onImageSizeChange( lines , columns );
  sh->setSize( lines , columns );
}

void TESession::onFontMetricChange(int height, int width)
{
  //kdDebug(1211)<<"TESession::onFontMetricChange " << height << " " << width << endl;
  if (connected) {
    font_h = height;
    font_w = width;
  }
}

bool TESession::sendSignal(int signal)
{
  return sh->kill(signal);
}

bool TESession::closeSession()
{
  autoClose = true;
  wantedClose = true;
  if (!sh->isRunning() || !sendSignal(SIGHUP))
  {
     // Forced close.
     TQTimer::singleShot(1, this, TQ_SLOT(done()));
  }
  return true;
}

void TESession::feedSession(const TQString &text)
{
  emit disableMasterModeConnections();
  setListenToKeyPress(true);
  te->emitText(text);
  setListenToKeyPress(false);
  emit enableMasterModeConnections();
}

void TESession::sendSession(const TQString &text)
{
  TQString newtext=text;
  newtext.append("\r");
  feedSession(newtext);
}

void TESession::renameSession(const TQString &name)
{
  title=name;
  emit renameSession(this,name);
}

TESession::~TESession()
{
 //kdDebug(1211) << "disconnnecting..." << endl;
  TQObject::disconnect( sh, TQ_SIGNAL( done(int) ),
                       this, TQ_SLOT( done(int) ) );
  delete em;
  delete sh;

  delete zmodemProc;
}

void TESession::setConnect(bool c)
{
  connected=c;
  em->setConnect(c);
  setListenToKeyPress(c);
}

void TESession::setListenToKeyPress(bool l)
{
  em->setListenToKeyPress(l);
}

void TESession::done() {
  emit processExited(sh);
  emit done(this);
}

void TESession::done(int exitStatus)
{
  if (!autoClose)
  {
    userTitle = i18n("<Finished>");
    emit updateTitle(this);
    return;
  }
  if (!wantedClose && (exitStatus || sh->signalled()))
  {
    if (sh->normalExit())
      KNotifyClient::event(winId, "Finished", i18n("Session '%1' exited with status %2.").arg(title).arg(exitStatus));
    else if (sh->signalled())
    {
      if (sh->coreDumped())
        KNotifyClient::event(winId, "Finished", i18n("Session '%1' exited with signal %2 and dumped core.").arg(title).arg(sh->exitSignal()));
      else
        KNotifyClient::event(winId, "Finished", i18n("Session '%1' exited with signal %2.").arg(title).arg(sh->exitSignal()));
    }
    else
      KNotifyClient::event(winId, "Finished", i18n("Session '%1' exited unexpectedly.").arg(title));
  }
  emit processExited(sh);
  emit done(this);
}

void TESession::terminate()
{
  delete this;
}

TEmulation* TESession::getEmulation()
{
  return em;
}

// following interfaces might be misplaced ///

int TESession::schemaNo()
{
  return schema_no;
}

int TESession::encodingNo()
{
  return encoding_no;
}

int TESession::keymapNo()
{
  return em->keymapNo();
}

TQString TESession::keymap()
{
  return em->keymap();
}

int TESession::fontNo()
{
  return font_no;
}

const TQString & TESession::Term()
{
  return term;
}

const TQString & TESession::SessionId()
{
  return sessionId;
}

void TESession::setSchemaNo(int sn)
{
  schema_no = sn;
}

void TESession::setEncodingNo(int index)
{
  encoding_no = index;
}
  
void TESession::setKeymapNo(int kn)
{
  em->setKeymap(kn);
}

void TESession::setKeymap(const TQString &id)
{
  em->setKeymap(id);
}

void TESession::setFontNo(int fn)
{
  font_no = fn;
}

void TESession::setMetaAsAltMode(bool mode)
{
  if (em)
    em->setMetaKeyMode(mode);
}

void TESession::setTitle(const TQString& _title)
{
  title = _title;
  //kdDebug(1211)<<"Session setTitle " <<  title <<endl;
}

const TQString& TESession::Title()
{
  return title;
}

void TESession::setIconName(const TQString& _iconName)
{
  iconName = _iconName;
}

void TESession::setIconText(const TQString& _iconText)
{
  iconText = _iconText;
  //kdDebug(1211)<<"Session setIconText " <<  iconText <<endl;
}

const TQString& TESession::IconName()
{
  return iconName;
}

const TQString& TESession::IconText()
{
  return iconText;
}

bool TESession::testAndSetStateIconName (const TQString& newname)
{
  if (newname != stateIconName)
    {
      stateIconName = newname;
      return true;
    }
  return false;
}

void TESession::setHistory(const HistoryType &hType)
{
  em->setHistory(hType);
}

const HistoryType& TESession::history()
{
  return em->history();
}

void TESession::clearHistory()
{
  if (history().isOn()) {
    int histSize = history().getSize();
    setHistory(HistoryTypeNone());
    if (histSize)
      setHistory(HistoryTypeBuffer(histSize));
    else
      setHistory(HistoryTypeFile());
  }
}

TQStrList TESession::getArgs()
{
  return args;
}

TQString TESession::getPgm()
{
  return pgm;
}

TQString TESession::getCwd()
{
#ifdef HAVE_PROC_CWD
  if (cwd.isEmpty()) {
    TQFileInfo Cwd(TQString("/proc/%1/cwd").arg(sh->pid()));
    if(Cwd.isSymLink())
      return Cwd.readLink();
  }
#endif /* HAVE_PROC_CWD */
  return cwd;
}

bool TESession::isMonitorActivity() { return monitorActivity; }
bool TESession::isMonitorSilence() { return monitorSilence; }
bool TESession::isMasterMode() { return masterMode; }

void TESession::setMonitorActivity(bool _monitor)
{
  monitorActivity=_monitor;
  notifiedActivity=false;
}

void TESession::setMonitorSilence(bool _monitor)
{
  if (monitorSilence==_monitor)
    return;

  monitorSilence=_monitor;
  if (monitorSilence)
    monitorTimer->start(silence_seconds*1000,true);
  else
    monitorTimer->stop();
}

void TESession::setMonitorSilenceSeconds(int seconds)
{
  silence_seconds=seconds;
  if (monitorSilence) {
    monitorTimer->start(silence_seconds*1000,true);
  }
}

void TESession::setMasterMode(bool _master)
{
  masterMode=_master;
}

void TESession::setAddToUtmp(bool set)
{
  add_to_utmp = set;
}

void TESession::setXonXoff(bool set)
{
  xon_xoff = set;
}

void TESession::slotZModemDetected()
{
  if (!zmodemBusy)
  {
    TQTimer::singleShot(10, this, TQ_SLOT(emitZModemDetected()));
    zmodemBusy = true;
  }
}

void TESession::emitZModemDetected()
{
  emit zmodemDetected(this);
}

void TESession::cancelZModem()
{
  sh->send_bytes("\030\030\030\030", 4); // Abort
  zmodemBusy = false;
}

void TESession::startZModem(const TQString &zmodem, const TQString &dir, const TQStringList &list)
{
  zmodemBusy = true;
  zmodemProc = new TDEProcIO;

  (*zmodemProc) << zmodem << "-v";
  for(TQStringList::ConstIterator it = list.begin();
      it != list.end();
      ++it)
  {
     (*zmodemProc) << (*it);
  }

  if (!dir.isEmpty())
     zmodemProc->setWorkingDirectory(dir);
  zmodemProc->start(TDEProcIO::NotifyOnExit, false);

  // Override the read-processing of TDEProcIO
  disconnect(zmodemProc,TQ_SIGNAL (receivedStdout (TDEProcess *, char *, int)), 0, 0);
  connect(zmodemProc,TQ_SIGNAL (receivedStdout (TDEProcess *, char *, int)),
          this, TQ_SLOT(zmodemSendBlock(TDEProcess *, char *, int)));
  connect(zmodemProc,TQ_SIGNAL (receivedStderr (TDEProcess *, char *, int)),
          this, TQ_SLOT(zmodemStatus(TDEProcess *, char *, int)));
  connect(zmodemProc,TQ_SIGNAL (processExited(TDEProcess *)),
          this, TQ_SLOT(zmodemDone()));

  disconnect( sh,TQ_SIGNAL(block_in(const char*,int)), this, TQ_SLOT(onRcvBlock(const char*,int)) );
  connect( sh,TQ_SIGNAL(block_in(const char*,int)), this, TQ_SLOT(zmodemRcvBlock(const char*,int)) );
  connect( sh,TQ_SIGNAL(buffer_empty()), this, TQ_SLOT(zmodemContinue()));

  zmodemProgress = new ZModemDialog(te->topLevelWidget(), false,
                                    i18n("ZModem Progress"));

  connect(zmodemProgress, TQ_SIGNAL(user1Clicked()),
          this, TQ_SLOT(zmodemDone()));

  zmodemProgress->show();
}

void TESession::zmodemSendBlock(TDEProcess *, char *data, int len)
{
  sh->send_bytes(data, len);
//  tqWarning("<-- %d bytes", len);
  if (sh->buffer_full())
  {
    zmodemProc->suspend();
//    tqWarning("ZModem suspend");
  }
}

void TESession::zmodemContinue()
{
  zmodemProc->resume();
//  tqWarning("ZModem resume");
}

void TESession::zmodemStatus(TDEProcess *, char *data, int len)
{
  TQCString msg(data, len+1);
  while(!msg.isEmpty())
  {
     int i = msg.find('\015');
     int j = msg.find('\012');
     TQCString txt;
     if ((i != -1) && ((j == -1) || (i < j)))
     {
       msg = msg.mid(i+1);
     }
     else if (j != -1)
     {
       txt = msg.left(j);
       msg = msg.mid(j+1);
     }
     else
     {
       txt = msg;
       msg.truncate(0);
     }
     if (!txt.isEmpty())
       zmodemProgress->addProgressText(TQString::fromLocal8Bit(txt));
  }
}

void TESession::zmodemRcvBlock(const char *data, int len)
{
  TQByteArray ba;
  ba.duplicate(data, len);
  zmodemProc->writeStdin(ba);
//  tqWarning("--> %d bytes", len);
}

void TESession::zmodemDone()
{
  if (zmodemProc)
  {
    delete zmodemProc;
    zmodemProc = 0;
    zmodemBusy = false;

    disconnect( sh,TQ_SIGNAL(block_in(const char*,int)), this ,TQ_SLOT(zmodemRcvBlock(const char*,int)) );
    disconnect( sh,TQ_SIGNAL(buffer_empty()), this, TQ_SLOT(zmodemContinue()));
    connect( sh,TQ_SIGNAL(block_in(const char*,int)), this, TQ_SLOT(onRcvBlock(const char*,int)) );

    sh->send_bytes("\030\030\030\030", 4); // Abort
    sh->send_bytes("\001\013\n", 3); // Try to get prompt back
    zmodemProgress->done();
  }
}


bool TESession::processDynamic(const TQCString &fun, const TQByteArray &data, TQCString& replyType, TQByteArray &replyData)
{
    if (fullScripting)
    {
      if (fun == "feedSession(TQString)")
      {
        TQString arg0;
        TQDataStream arg( data, IO_ReadOnly );
        arg >> arg0;
        feedSession(arg0);
        replyType = "void";
        return true;
      }
      else if (fun == "sendSession(TQString)")
      {
        TQString arg0;
        TQDataStream arg( data, IO_ReadOnly );
        arg >> arg0;
        sendSession(arg0);
        replyType = "void";
        return true;
      }
    }
    return SessionIface::processDynamic(fun, data, replyType, replyData);

}

QCStringList TESession::functionsDynamic()
{
    QCStringList funcs = SessionIface::functionsDynamic();
    if (fullScripting)
    {
       funcs << "void feedSession(TQString text)";
       funcs << "void sendSession(TQString text)";
    }
    return funcs;
}


void TESession::onRcvBlock( const char* buf, int len )
{
    em->onRcvBlock( buf, len );
    emit receivedData( TQString::fromLatin1( buf, len ) );
}

void TESession::print( TQPainter &paint, bool friendly, bool exact )
{
    te->print(paint, friendly, exact);
}

TQString TESession::schema()
{
  TQString currentSchema;
  emit getSessionSchema(this, currentSchema);
  return currentSchema;
}

void TESession::setSchema(const TQString &schema)
{
  emit setSessionSchema(this, schema);
}

TQString TESession::font()
{
  return te->getVTFont().toString();
}

void TESession::setFont(const TQString &font)
{
  TQFont tmp;
  if (tmp.fromString(font))
    te->setVTFont(tmp);
  else
    kdWarning()<<"unknown font: "<<font<<endl;
}

TQString TESession::encoding()
{
  return em->codec()->name();
}

void TESession::setEncoding(const TQString &encoding)
{
  emit setSessionEncoding(this, encoding);
}

TQString TESession::keytab()
{
   return keymap();
}

void TESession::setKeytab(const TQString &keytab)
{
   setKeymap(keytab);
   emit updateSessionKeytab(this, keytab);
   emit updateSessionConfig(this);
}

TQSize TESession::size()
{
  return em->imageSize();
}

void TESession::setSize(TQSize size)
{
  if ((size.width() <= 1) || (size.height() <= 1))
     return;
  
  emit resizeSession(this, size);
}

#include "session.moc"
