/* This file is part of the KDE project
 *
 * Copyright (C) 2000 Richard Moore <rich@kde.org>
 *               2000 Wynn Wilkes <wynnw@caldera.com>
 *
 * 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 "kjavaprocess.h"

#include <kdebug.h>
#include <kshell.h>
#include <kio/kprotocolmanager.h>

#include <QtCore/QTextStream>
#include <QtCore/QMap>

#include <config.h>

class KJavaProcessPrivate
{
friend class KJavaProcess;
private:
    QString jvmPath;
    QString classPath;
    QString mainClass;
    QString extraArgs;
    QString classArgs;
    QMap<QString, QString> systemProps;
};

KJavaProcess::KJavaProcess( QObject* parent )
	: KProcess( parent ),
	d(new KJavaProcessPrivate)

{
    connect( this, SIGNAL( readyReadStandardOutput() ),
             this, SLOT( slotReceivedData() ) );
    connect( this, SIGNAL( finished( int, QProcess::ExitStatus ) ),
             this, SLOT( slotExited() ) );
    connect( this, SIGNAL( error(  QProcess::ProcessError ) ),
             this, SLOT( slotExited() ) );

    d->jvmPath = "java";
    d->mainClass = "-help";
}

KJavaProcess::~KJavaProcess()
{
    if ( state() != NotRunning )
    {
        kDebug(6100) << "stopping java process";
        stopJava();
    }
    delete d;
}

bool KJavaProcess::isRunning()
{
   return state() != NotRunning;
}

bool KJavaProcess::startJava()
{
   return invokeJVM();
}

void KJavaProcess::stopJava()
{
   killJVM();
}

void KJavaProcess::setJVMPath( const QString& path )
{
   d->jvmPath = path;
}

void KJavaProcess::setClasspath( const QString& classpath )
{
    d->classPath = classpath;
}

void KJavaProcess::setSystemProperty( const QString& name,
                                      const QString& value )
{
   d->systemProps.insert( name, value );
}

void KJavaProcess::setMainClass( const QString& className )
{
   d->mainClass = className;
}

void KJavaProcess::setExtraArgs( const QString& args )
{
   d->extraArgs = args;
}

void KJavaProcess::setClassArgs( const QString& args )
{
   d->classArgs = args;
}

//Private Utility Functions used by the two send() methods
QByteArray KJavaProcess::addArgs( char cmd_code, const QStringList& args )
{
    //the buffer to store stuff, etc.
    QByteArray buff;
    QTextStream output( &buff, QIODevice::ReadWrite );
    const char sep = 0;

    //make space for the command size: 8 characters...
    const QByteArray space( "        " );
    output << space;

    //write command code
    output << cmd_code;

    //store the arguments...
    if( args.isEmpty() )
    {
        output << sep;
    }
    else
    {
        QStringList::ConstIterator it = args.begin();
        const QStringList::ConstIterator itEnd = args.end();
        for( ; it != itEnd; ++it )
        {
            if( !(*it).isEmpty() )
            {
                output << (*it).toLocal8Bit();
            }
            output << sep;
        }
    }

    return buff;
}

void KJavaProcess::storeSize( QByteArray* buff )
{
    const int size = buff->size() - 8;  //subtract out the length of the size_str
    const QString size_str = QString("%1").arg( size, 8 );
    kDebug(6100) << "KJavaProcess::storeSize, size = " << size_str;

    for( int i = 0; i < 8; ++i )
        buff->data()[ i ] = size_str[i].toLatin1();
}

void KJavaProcess::send( char cmd_code, const QStringList& args )
{
    if( isRunning() )
    {
        QByteArray buff = addArgs( cmd_code, args );
        storeSize( &buff );
        kDebug(6100) << "<KJavaProcess::send " << (int)cmd_code;
        write( buff );
    }
}

void KJavaProcess::send( char cmd_code, const QStringList& args,
                         const QByteArray& data )
{
    if( isRunning() )
    {
        kDebug(6100) << "KJavaProcess::send, qbytearray is size = " << data.size();

        QByteArray buff = addArgs( cmd_code, args );
        buff += data;

        storeSize( &buff );
        write( buff );
    }
}

bool KJavaProcess::invokeJVM()
{
    QStringList args;

    if( !d->classPath.isEmpty() )
    {
        args << "-classpath";
        args << d->classPath;
    }

    //set the system properties, iterate through the qmap of system properties
    QMap<QString,QString>::ConstIterator it = d->systemProps.constBegin();
    const QMap<QString,QString>::ConstIterator itEnd = d->systemProps.constEnd();

    for( ; it != itEnd; ++it )
    {
        if( !it.key().isEmpty() )
        {
            QString currarg = "-D" + it.key();
            if( !it.value().isEmpty() )
                currarg += '=' + it.value();
            args << currarg;
        }
    }

    //load the extra user-defined arguments
    if( !d->extraArgs.isEmpty() )
    {
        KShell::Errors err;
        args += KShell::splitArgs( d->extraArgs, KShell::AbortOnMeta, &err );
        if( err != KShell::NoError )
            kWarning(6100) << "Extra args for JVM cannot be parsed, arguments = " << d->extraArgs;

    }

    args << d->mainClass;

    if ( !d->classArgs.isNull() )
        args << d->classArgs;

    kDebug(6100) << "Invoking JVM" << d->jvmPath << "now...with arguments = " << KShell::joinArgs(args);

    setOutputChannelMode(KProcess::SeparateChannels);
    setProgram( d->jvmPath, args );
    start();

    return waitForStarted();
}

void KJavaProcess::killJVM()
{
   closeReadChannel( StandardOutput );
   terminate();
}

/*  In this method, read one command and send it to the d->appletServer
 *  then return, so we don't block the event handling
 */
void KJavaProcess::slotReceivedData()
{
    //read out the length of the message,
    //read the message and send it to the applet server
    char length[9] = { 0 };
    const int num_bytes = read( length, 8 );
    if( num_bytes == -1 )
    {
        kError(6100) << "could not read 8 characters for the message length!!!!" << endl;
        return;
    }

    const QString lengthstr( length );
    bool ok;
    const int num_len = lengthstr.toInt( &ok );
    if( !ok )
    {
        kError(6100) << "could not parse length out of: " << lengthstr << endl;
        return;
    }

    //now parse out the rest of the message.
    char* const msg = new char[num_len];
    const int num_bytes_msg = read( msg, num_len );
    if( num_bytes_msg == -1 || num_bytes_msg != num_len )
    {
        kError(6100) << "could not read the msg, num_bytes_msg = " << num_bytes_msg << endl;
        delete[] msg;
        return;
    }

    emit received( QByteArray( msg, num_len ) );
    delete[] msg;
}

void KJavaProcess::slotExited()
{
    int status = -1;
    if ( exitStatus() == NormalExit ) {
     status = exitCode();
    }
    kDebug(6100) << "jvm exited with status " << status; 
    emit exited(status);
}

#include "kjavaprocess.moc"
