/*  This file is part of the KDE project
    Copyright (C) 2000-2002 Alexander Neundorf <neundorf@kde.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include "program.h"

#include <config-runtime.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>

#include <kdebug.h>

Program::Program(const QStringList &args)
:m_pid(0)
,mArgs(args)
,mStarted(false)
{
}

Program::~Program()
{
   if (m_pid!=0)
   {
      ::close(mStdin[0]);
      ::close(mStdout[0]);
      ::close(mStderr[0]);

      ::close(mStdin[1]);
      ::close(mStdout[1]);
      ::close(mStderr[1]);

      int s(0);
      //::wait(&s);
      ::waitpid(m_pid,&s,0);
      this->kill();
      ::waitpid(m_pid,&s,WNOHANG);
   };
}

bool Program::start()
{
   if (mStarted) return false;
   if (pipe(mStdout)==-1) return false;
   if (pipe(mStdin )==-1) return false;
   if (pipe(mStderr )==-1) return false;

   int notificationPipe[2];
   if (pipe(notificationPipe )==-1) return false;

   m_pid=fork();

   if (m_pid>0)
   {
      //parent
      ::close(mStdin[0]);
      ::close(mStdout[1]);
      ::close(mStderr[1]);
      ::close(notificationPipe[1]);
      mStarted=true;
      fd_set notifSet;
      FD_ZERO(&notifSet);
      FD_SET(notificationPipe[0],&notifSet);
      struct timeval tv;
      //wait up to five seconds

      kDebug(7101)<<"**** waiting for notification";
      //0.2 sec
      tv.tv_sec=0;
      tv.tv_usec=1000*200;
      int result=::select(notificationPipe[0]+1,&notifSet,0,0,&tv);
/*      if (result<1)
      {
         kDebug(7101)<<"**** waiting for notification: failed "<<result;
         return false;
      }
      else*/
      if(result==1)
      {
         char buf[256];
         result=::read(notificationPipe[0],buf,256);
         //if execvp() failed the child sends us "failed"
         if (result>0)
            return false;
      };
      kDebug(7101)<<"**** waiting for notification: succeeded"<<result;
      return true;
   }
   else if (m_pid==-1)
   {
      //failed
      return false;
   }
   else if (m_pid==0)
   {
      ::close(notificationPipe[0]);

      //child
      dup2(mStdin[0], 0);
      dup2(mStdout[1], 1);
      dup2(mStderr[1], 2);

      ::close(mStdin[1]);
      ::close(mStdout[0]);
      ::close(mStderr[0]);

      fcntl(mStdin[0], F_SETFD, FD_CLOEXEC);
      fcntl(mStdout[1], F_SETFD, FD_CLOEXEC);
      fcntl(mStderr[1], F_SETFD, FD_CLOEXEC);

      char **arglist=(char**)malloc((mArgs.count()+1)*sizeof(char*));
      int c=0;

      for (QStringList::const_iterator it=mArgs.constBegin(); it!=mArgs.constEnd(); ++it)
      {
         arglist[c]=(char*)malloc((*it).length()+1);
         strcpy(arglist[c], (*it).toLatin1());
         c++;
      }
      arglist[mArgs.count()]=0;
      //make parsing easier
      putenv(strdup("LANG=C"));
      execvp(arglist[0], arglist);
      //we only get here if execvp() failed
      ::write(notificationPipe[1],"failed",strlen("failed"));
      ::close(notificationPipe[1]);
      _exit(-1);
   };
   return false;
}

bool Program::isRunning()
{
   return mStarted;
}

int Program::select(int secs, int usecs, bool& stdoutReceived, bool& stderrReceived/*, bool& stdinWaiting*/)
{
   stdoutReceived=false;
   stderrReceived=false;

   struct timeval tv;
   tv.tv_sec=secs;
   tv.tv_usec=usecs;

   fd_set readFDs;
   FD_ZERO(&readFDs);
   FD_SET(stdoutFD(),&readFDs);
   FD_SET(stderrFD(),&readFDs);

   int maxFD=stdoutFD();
   if (stderrFD()>maxFD) maxFD=stderrFD();

   /*fd_set writeFDs;
   FD_ZERO(&writeFDs);
   FD_SET(stdinFD(),&writeFDs);
   if (stdinFD()>maxFD) maxFD=stdinFD();*/
   maxFD++;

   int result=::select(maxFD,&readFDs,/*&writeFDs*/0,0,&tv);
   if (result>0)
   {
      stdoutReceived=FD_ISSET(stdoutFD(),&readFDs);
      stderrReceived=FD_ISSET(stderrFD(),&readFDs);
      //stdinWaiting=(FD_ISSET(stdinFD(),&writeFDs));
   };
   return result;
}

int Program::kill()
{
   if (m_pid==0)
      return -1;
   return ::kill(m_pid, SIGTERM);
   //::kill(m_pid, SIGKILL);
}

