#include <stdio.h> // popen()
#include <stdlib.h> // system()
#include <sys/types.h> // pid_t
#include <sys/wait.h> // pid_t
#include <unistd.h> // fork()

#include "EShellProcess.h"
#include "EStringList.h"
#include <signal.h> // signal(), sighandler_t
#include <setjmp.h> // longjmp()

using std::string;
using std::endl;

sigjmp_buf EShellProcess_ctrl_c_jmp_buffer;

void EShellProcess_SIGINT(int)
{
        std::cout << "^C" << std::endl; // this really shouldn't be here, but i find it useful.
        ::siglongjmp( EShellProcess_ctrl_c_jmp_buffer, 1 );
}

int
EShellProcess::system( const std::string &cmdline, std::ostream & os  )
{
        return ::system( cmdline.c_str() ) / 256; // i don't like this /256. it's against the docs, but the return values aren't what the docs say :/
}

int
EShellProcess::fork( const std::string &cmdline, std::ostream & os  )
{ // my very first fork()/exec()
        pid_t pid;
        EStringList tokens = EStringList::tokenize( cmdline );
                
        pid = ::fork();
        if( pid == -1 )
        {
                os << "EShellProcess::fork("<<cmdline<<"): error fork()ing." << endl;
                return 1;
        }
        if( pid > 0 ) return 0; // parent, go away.
        // child...

        // i hate to do this array stuff, but i see no other
        // way around it without finding a template class for
        // dynamic arrays, and i'm not up for that right now...
        static const int maxargs = 100;
        char * args[maxargs];
        int tcount = tokens.count();
        int i = 0;
        string tok;
        string filearg;
        for( ; i < tcount; i++ )
        {
                tok = tokens.shift();
                if( i == 0 ) filearg = tok;
                if( tok.empty() ) continue;
                //os << "adding token ["<<tok<<"]"<<endl;
                args[i] = const_cast<char *>( tok.c_str() );
                // this ^^^^^^^^^^^ const_cast is theoretically okay
                // because we're not gonna de-allocate the
                // array and this process will never pass
                // control back to anyone else, so tokens
                // shouldn't be unduly molested behind our
                // back. i won't swear that it's safe, though :/.
        }
        if( i == maxargs )
        {
                os << __FILE__<<":"<<__LINE__<<": EShellProcess::fork(): you've met or overstepped the\n"
                   << "hard-coded limit of "<< maxargs<<" arguments.\n"
                   <<"Please contact the maintainer of this code with an example of how you're using it (stephan@wanderinghorse.net), "
                   << "or fix this bug and send him your fix :)."
                   << endl;
        }
        for( ; i < maxargs; i++ )
        {
                args[i] = NULL;
        }
        execvp( filearg.c_str(), args );
        _exit(0);
        return 0;
}

int
EShellProcess::pipe( const std::string &cmdline, std::ostream & os )
{
        if( cmdline.empty() )
        {
                os << "EShellProcess::pipe(): cannot run an empty command :(";
                return 1;
        }
        FILE *fp = 0;
        fp = ::popen( cmdline.c_str(), "r" );
        if( ! fp )
        {
                os << "EShellProcess::pipe(): popen("<<cmdline<<") failed :(" << std::endl;
                return 1;
        }
        ::fwrite( cmdline.c_str(), sizeof( char ), cmdline.size(), fp );
#define RESTORE_SIGNALS ::signal( SIGINT, old_sighandler )
        typedef void (*signalfunc)(int);
        signalfunc old_sighandler = ::signal( SIGINT, EShellProcess_SIGINT );
        int getcchar;
        if( 0 == ::sigsetjmp( EShellProcess_ctrl_c_jmp_buffer, 1 ) )
        {
                while( ! ::feof( fp ) )
                {
                        getcchar = ::getc( fp );
                        if( getcchar == EOF ) break;
                        os <<(unsigned char) getcchar;
                        if( ! os && (os != std::cerr) )
                        {
                                RESTORE_SIGNALS;
                                std::cerr << "EShellProcess::pipe(): error in output stream!" << std::endl;
                                return 1;
                        }
                }
        }
        RESTORE_SIGNALS;
        int ret = ::pclose( fp );
        return (ret == 0) ? 0 : (ret / 256);
}


