/******************************************************************************
*
* Copyright (C) 2002 Hugo PEREIRA <mailto: hugo.pereira@free.fr>
*
* This 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 software 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, see <http://www.gnu.org/licenses/>.
*
*******************************************************************************/

#include "JobInterface.h"
#include "CustomProcess.h"
#include "Debug.h"
#include "File.h"
#include "Job.h"
#include "JobThread.h"
#include "QOrderedSet.h"
#include "Util.h"
#include "XmlOptions.h"

#include <sys/types.h>
#include <signal.h>

#include <errno.h>
#include <string.h>

//_______________________________________________
JobInterface::JobInterface( QObject *parent ):
    QObject( parent ),
    Counter( "JobInterface" ),
    thread_( new JobThread( this ) )
{
    Debug::Throw("JobInterface::JobInterface.\n");
    connect( thread_, SIGNAL(finished()), SLOT(_commandCompleted()) );
}

//__________________________________________________________________________________
void JobInterface::addCommand( JobCommand command )
{

    Debug::Throw( "JobInterface::addCommand.\n" );

    // append command to queue
    commands_ << command;

    // run command if there is none pending
    if( commands_.size() == 1 ) _runCommand();

}

//____________________________________________________________
void JobInterface::_error( QProcess::ProcessError )
{

    Q_ASSERT( !commands_.empty() );
    JobCommand command( commands_.front() );

    emit messageAvailable( QString( tr( "Failed to %1" ) ).arg( command.name() ) );

    // remove command and process to next
    emit commandCompleted( command );
    commands_.removeFirst();
    _runCommand();

    return;
}

//______________________________________________________________
void JobInterface::_commandCompleted( int returnCode, QProcess::ExitStatus status )
{

    Q_ASSERT( !commands_.empty() );
    JobCommand command( commands_.front() );
    if( status != QProcess::NormalExit || ( _hasProcess() && process_->error() == QProcess::FailedToStart ) || returnCode != 0 )
    {

        emit commandCompleted( command );
        commands_.removeFirst();
        _runCommand();
        return;

    }

    // run action depending on current type
    switch( command.type() )
    {

        case JobCommand::Type::JobList:
        {
            // process summary output
            _processJobList();

            emit commandCompleted( command );
            commands_.removeFirst();
            _runCommand();
            break;
        }

        default:
        {
            emit commandCompleted( command );
            commands_.removeFirst();
            _runCommand();
            break;
        }

    }

    return;

}

//________________________________________________________________________
void JobInterface::_processJobList()
{

    Debug::Throw( "JobInterface::_processJobList.\n" );

    // read jobs from thread
    const Job::Set jobs( thread_->jobs() );

    // list of users
    UserSet users;
    users << UserSet::allUsers();
    users << Util::user();

    QOrderedSet<QString> jobUsers;
    for( const auto& job:jobs )
    { jobUsers.insert( job.user() ); }

    for( const auto& user:jobUsers )
    { if( !users.contains( user ) ) users << user; }

    emit jobListReady( jobs );
    emit userListReady( users );
}

//___________________________________________________________________
void JobInterface::_readStdout()
{
    process_->setReadChannel( QProcess::StandardOutput );
    stdoutBuffer_.append( QString::fromLocal8Bit( process_->readAll() ) );
}

//___________________________________________________________________
void JobInterface::_readStderr()
{
    process_->setReadChannel( QProcess::StandardError );
    stdoutBuffer_.append( QString::fromLocal8Bit( process_->readAll() ) );
}

//____________________________________________________________
void JobInterface::_resetBuffers()
{
    Debug::Throw( "JobInterface::_resetBuffer.\n" );
    stdoutBuffer_.clear();
    stderrBuffer_.clear();
}

//__________________________________________________________________________________
bool JobInterface::_runCommand()
{

    if( commands_.empty() )
    {
        Debug::Throw( "JobInterface::_runCommand - ready \n" );
        emit ready();
        return false;
    }

    // reset buffer
    _resetBuffers();
    _resetProcess();

    // front command
    JobCommand command( commands_.front() );

    // take action depending on command type
    switch( command.type() )
    {

        case JobCommand::Type::JobList:
        {

            thread_->start();
            return true;

        }

        case JobCommand::Type::Signal:
        {

            // send "STOP" signal to all selected processes
            bool first( true );
            int signal( 0 );
            for( const auto& argument:command.arguments() )
            {

                if( first )
                {

                    first = false;
                    signal = argument.toInt();

                } else kill( argument.toInt(), signal );

            }
            break;

        }

        default:
        break;
    }

    emit commandCompleted( command );
    commands_.removeFirst();
    return _runCommand();

}

//___________________________________________
void JobInterface::_resetProcess()
{
    if( process_ )
    {
        process_->deleteLater();
        process_ = nullptr;
    }
}

//____________________________________________________________
CustomProcess& JobInterface::_newProcess()
{

    _resetProcess();

    process_ = new CustomProcess( this );
    connect( process_, SIGNAL(error(QProcess::ProcessError)), SLOT(_error(QProcess::ProcessError)) );
    connect( process_, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(_commandCompleted(int,QProcess::ExitStatus)) );
    connect( process_, SIGNAL(readyReadStandardOutput()), SLOT(_readStdout()) );
    connect( process_, SIGNAL(readyReadStandardError()), SLOT(_readStderr()) );
    return *process_;

}


//____________________________________________________________
qint64 JobInterface::_memoryStringToInteger( const QString& valueString )
{

    // default return value
    qint64 value(0);

    // try read last digit to check if unit has to be changed
    if( valueString.size() >= 2 )
    {

        QString last( valueString.mid( valueString.size()-1, 1 ) );
        if( last == "m" || last == "M" )
        {

            bool ok( true );
            value = valueString.left( valueString.size()-1 ).toLongLong( &ok );
            if( ok ) value *= 1024;

        }

    } else {

        value = valueString.toLongLong();

    }

    return value;

}

//____________________________________________________________
qint64 JobInterface::_cpuTimeToInteger( const QString& value )
{

    QRegExp regExp( "(\\d+):(\\d+)\\.(\\d+)" );

    if( value.indexOf( regExp ) >= 0 ) return 1000*(regExp.cap(1).toLongLong()*60 + regExp.cap(2).toLongLong() ) + 10*regExp.cap(3).toLongLong();
    else return 0;

}
