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

    Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008

    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.
*/

// Own
#include "Pty.h"

// System
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <termios.h>

// Konsole
#include "kpty.h"

using namespace Konsole;

void Pty::donePty()
{
  emit done(exitStatus());
}

void Pty::setWindowSize(int lines, int cols)
{
  _windowColumns = cols;
  _windowLines = lines;

  if (pty()->masterFd() >= 0)
    pty()->setWinSize(lines, cols);
}
QSize Pty::windowSize() const
{
    return QSize(_windowColumns,_windowLines);
}

void Pty::setXonXoff(bool enable)
{
  _xonXoff = enable;

  if (pty()->masterFd() >= 0)
  {
    struct ::termios ttmode;
    pty()->tcGetAttr(&ttmode);
    if (!enable)
      ttmode.c_iflag &= ~(IXOFF | IXON);
    else
      ttmode.c_iflag |= (IXOFF | IXON);
    if (!pty()->tcSetAttr(&ttmode))
      qWarning("Unable to set terminal attributes.");
  }
}

void Pty::setUtf8Mode(bool enable)
{
#ifdef IUTF8 // XXX not a reasonable place to check it.
  _utf8 = enable;

  if (pty()->masterFd() >= 0)
  {
    struct ::termios ttmode;
    pty()->tcGetAttr(&ttmode);
    if (!enable)
      ttmode.c_iflag &= ~IUTF8;
    else
      ttmode.c_iflag |= IUTF8;
    if (!pty()->tcSetAttr(&ttmode))
      qWarning("Unable to set terminal attributes.");
  }
#endif
}

void Pty::setErase(char erase)
{
  _eraseChar = erase;

  if (pty()->masterFd() >= 0)
  {
    struct ::termios ttmode;

    pty()->tcGetAttr(&ttmode);

    ttmode.c_cc[VERASE] = erase;

    if (!pty()->tcSetAttr(&ttmode))
      qWarning("Unable to set terminal attributes.");
  }
}

char Pty::erase() const
{
  if (pty()->masterFd() >= 0)
  {
    qDebug() << "Getting erase char";
    struct ::termios ttyAttributes;
    pty()->tcGetAttr(&ttyAttributes);
    return ttyAttributes.c_cc[VERASE];
  }

  return _eraseChar;
}

void Pty::addEnvironmentVariables(const QStringList& environment)
{
    QListIterator<QString> iter(environment);
    while (iter.hasNext())
    {
        QString pair = iter.next();

        // split on the first '=' character
        int pos = pair.indexOf('=');

        if ( pos >= 0 )
        {
            QString variable = pair.left(pos);
            QString value = pair.mid(pos+1);

            //kDebug() << "Setting environment pair" << variable <<
            //    " set to " << value;

            setEnvironment(variable,value);
        }
    }
}

int Pty::start(const QString& program,
               const QStringList& programArguments,
               const QStringList& environment,
               ulong winid,
               bool addToUtmp
//               const QString& dbusService,
//               const QString& dbusSession)
    )
{
  clearArguments();

  setBinaryExecutable(program.toLatin1());

  addEnvironmentVariables(environment);

  QStringListIterator it( programArguments );
  while (it.hasNext())
    arguments.append( it.next().toUtf8() );

//  if ( !dbusService.isEmpty() )
//     setEnvironment("KONSOLE_DBUS_SERVICE",dbusService);
//  if ( !dbusSession.isEmpty() )
//     setEnvironment("KONSOLE_DBUS_SESSION", dbusSession);

  setEnvironment("WINDOWID", QString::number(winid));

  // unless the LANGUAGE environment variable has been set explicitly
  // set it to a null string
  // this fixes the problem where KCatalog sets the LANGUAGE environment
  // variable during the application's startup to something which
  // differs from LANG,LC_* etc. and causes programs run from
  // the terminal to display mesages in the wrong language
  //
  // this can happen if LANG contains a language which KDE
  // does not have a translation for
  //
  // BR:149300
  if (!environment.contains("LANGUAGE"))
      setEnvironment("LANGUAGE",QString());

  setUsePty(All, addToUtmp);

  pty()->open();

  struct ::termios ttmode;
  pty()->tcGetAttr(&ttmode);
  if (!_xonXoff)
    ttmode.c_iflag &= ~(IXOFF | IXON);
  else
    ttmode.c_iflag |= (IXOFF | IXON);
#ifdef IUTF8 // XXX not a reasonable place to check it.
  if (!_utf8)
    ttmode.c_iflag &= ~IUTF8;
  else
    ttmode.c_iflag |= IUTF8;
#endif

  if (_eraseChar != 0)
    ttmode.c_cc[VERASE] = _eraseChar;

  if (!pty()->tcSetAttr(&ttmode))
    qWarning("Unable to set terminal attributes.");

  pty()->setWinSize(_windowLines, _windowColumns);

  if ( K3Process::start(NotifyOnExit, (Communication) (Stdin | Stdout)) == false )
     return -1;

  resume(); // Start...
  return 0;

}

void Pty::setWriteable(bool writeable)
{
  struct stat sbuf;
  stat(pty()->ttyName(), &sbuf);
  if (writeable)
    chmod(pty()->ttyName(), sbuf.st_mode | S_IWGRP);
  else
    chmod(pty()->ttyName(), sbuf.st_mode & ~(S_IWGRP|S_IWOTH));
}

Pty::Pty()
    : _bufferFull(false),
      _windowColumns(0),
      _windowLines(0),
      _eraseChar(0),
      _xonXoff(true),
      _utf8(true)
{
  connect(this, SIGNAL(receivedStdout(K3Process *, char *, int )),
    this, SLOT(dataReceived(K3Process *,char *, int)));
  connect(this, SIGNAL(processExited(K3Process *)),
          this, SLOT(donePty()));
  connect(this, SIGNAL(wroteStdin(K3Process *)),
          this, SLOT(writeReady()));
  _pty = new KPty;

  setUsePty(All, false); // utmp will be overridden later
}

Pty::~Pty()
{
    delete _pty;
}

void Pty::writeReady()
{
  _pendingSendJobs.erase(_pendingSendJobs.begin());
  _bufferFull = false;
  doSendJobs();
}

void Pty::doSendJobs() {
  if(_pendingSendJobs.isEmpty())
  {
     emit bufferEmpty();
     return;
  }

  SendJob& job = _pendingSendJobs.first();


  if (!writeStdin( job.data(), job.length() ))
  {
    qWarning("Pty::doSendJobs - Could not send input data to terminal process.");
    return;
  }
  _bufferFull = true;
}

void Pty::appendSendJob(const char* s, int len)
{
  _pendingSendJobs.append(SendJob(s,len));
}

void Pty::sendData(const char* s, int len)
{
  appendSendJob(s,len);
  if (!_bufferFull)
     doSendJobs();
}

void Pty::dataReceived(K3Process *,char *buf, int len)
{
  emit receivedData(buf,len);
}

void Pty::lockPty(bool lock)
{
  if (lock)
    suspend();
  else
    resume();
}

int Pty::foregroundProcessGroup() const
{
    int pid = tcgetpgrp(pty()->masterFd());

    if ( pid != -1 )
    {
        return pid;
    }

    return 0;
}

//#include "moc_Pty.cpp"
