/**
    \file   ADM_jobControl
    \author mean fixounet@free.Fr (c) 2010

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

#include <QCoreApplication>
#include <QDir>

#ifdef _WIN32
#define UNICODE
#include "ADM_cpp.h"
#include <windows.h>
#include "ADM_win32.h"
#endif

#include "T_jobs.h"
#include "T_progress.h"
#include "ADM_default.h"
#include "ADM_coreJobs.h"
#include "pthread.h"

#ifdef _WIN32
    #define MKCLI() "avidemux_cli.exe"
    #define MKQT()  "avidemux.exe"
#else
    #ifdef __APPLE__
        #define STR(x) #x
        #define MKSTRING(x) STR(x)
        #define MKQT()  admExecutable("Avidemux" MKSTRING(AVIDEMUX_MAJOR_MINOR))
        #define MKCLI() admExecutable( "avidemux_cli")
    #else
        #define MKCLI() admExecutable("avidemux3_cli")
        #if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
            #define MKQT() admExecutable("avidemux3_qt6")
        #elif QT_VERSION >= QT_VERSION_CHECK(5,0,0)
            #define MKQT() admExecutable("avidemux3_qt5")
        #else
            #define MKQT() admExecutable("avidemux3_qt4")
        #endif
    #endif
#endif
/**
 * 
 * @param exeName
 * @return 
 */
const char *admExecutable(const char *exeName)
{
    static const char *separator =
#ifdef _WIN32
    "\\";
#else
    "/";
#endif
#define FULLNAME_ARRAY_SIZE 2048
    static char fullName[FULLNAME_ARRAY_SIZE]; // only use once per job, so "safe" to use static
    snprintf(fullName, FULLNAME_ARRAY_SIZE, "%s%s%s",
        QDir::toNativeSeparators(QCoreApplication::applicationDirPath()).toUtf8().constData(),
        separator, exeName);
#undef FULLNAME_ARRAY_SIZE
    return fullName;
}

/**
    \fn spawnerBoomerang
    \brief Allow to do class -> plain C -> class 
*/
static void *spawnerBoomerang(void *arg)
{
    spawnData    *data=(spawnData *)arg;
    data->me->runProcess(data);
    return NULL;
}

static bool spawnProcess(const char *processName, const vector <string> args)
{
    ADM_info("Starting <%s>\n",processName);
    string command = processName;
    for(int i=0;i<args.size();i++)
    {
        ADM_info("%d : %s\n",i,args.at(i).c_str());
        command += " ";
        command += args.at(i);
    }
    ADM_info("=>%s\n",command.c_str());
    ADM_info("==================== Start of spawner process job ================\n");

#ifdef _WIN32
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );
    // utf8 -> utf16
    const char *c=command.c_str();
    int size=utf8StringToWideChar(c,strlen(c),NULL);
    wchar_t* w = new wchar_t[size+1];

    utf8StringToWideChar(c,strlen(c),w);
    // Start the child process. 
    if( !CreateProcessW( 
        NULL,   // No module name (use command line)
        w,        // Command line
        NULL,           // Process handle not inheritable
        NULL,           // Thread handle not inheritable
        FALSE,          // Set handle inheritance to FALSE
        0,              // No creation flags
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory 
        &si,            // Pointer to STARTUPINFO structure
        &pi )           // Pointer to PROCESS_INFORMATION structure
    )
    {
        delete [] w;
        ADM_error("Cannot spawn process! (%s)\n",c);
        return false;
    }

    delete [] w;
    
#else
    system(command.c_str());
#endif
    ADM_info("==================== End of spawner process job ================\n");
    return true;
}

/**
    \fn runProcess
    \brief Thread can will run a process
*/
bool jobWindow::runProcess(spawnData *data)
{
    vector <string> args;
    if(portable)
        args.push_back("--portable");
    args.push_back("--nogui");
    char str[100];
    snprintf(str, 100, "--slave %d", localPort);
    args.push_back(str);
    string s = "--run \"";
    s += data->script;
    s += "\"";
    args.push_back(s);
    s = "--save \"";
    s += data->outputFile;
    s += "\"";
    args.push_back(s);
#ifndef _WIN32
    args.push_back("--quit > /tmp/prout.log");
#else
    args.push_back("--quit");
#endif
    return spawnProcess(data->exeName,args);
}

static string escapeString(const string &in)
{
#ifdef _WIN32
    return in;
#else
    if (std::string::npos == in.find("\"") &&
        std::string::npos == in.find("\\"))
        return in;
    string out;
    for (size_t i = 0; i < in.size(); i++)
    {
        switch(in[i])
        {
            case '\"':
            case '\\':
                out += "\\";
            default:
                out += in[i];
        }
    }
    return out;
#endif
}

/**
    \fn spawnChild
    \brief Spawn a child to execute a commande
*/
bool jobWindow::spawnChild(const char *exeName, const string &script, const string &outputFile)
{
    spawnData data;
    data.script = escapeString(script);
    data.outputFile = escapeString(outputFile);
    data.exeName = exeName;
    data.me=this;
    pthread_t threadId;
    // Have to spawn a thread that will handle the exec...
    if (pthread_create(&threadId, NULL, spawnerBoomerang, &data))
    {
        ADM_error("Spawn failed\n");
        return false;
    }
    // Everything is fine, give process 1 second to use the stack allocated data....
    ADM_usleep(1000*1000LL);
    QApplication::processEvents();
    ADM_info("Spawning successfull\n");
    return true;
}
 
/**
    \fn runOneJob
*/
bool jobWindow::runOneJob( ADMJob &job)   
{
    bool r=false;
    job.startTime=ADM_getSecondsSinceEpoch();
    job.status=ADM_JOB_RUNNING;
    ADM_commandSocket *runSocket=NULL;
    ADM_socketMessage msg;
    uint32_t v, version, wait=500; // by default, give application 500 ms to exit

    ADMJob::jobUpdate(job);
    refreshList();

    // 1- Start listening to socket

    // 2- Spawn  child
    string ScriptFullPath = ADM_getJobDir(); // already separator-terminated
    ScriptFullPath += job.scriptName;
    const char *avidemuxVersion = ui.checkBoxUseQt4->isChecked() ? MKQT() : MKCLI();
#ifdef _WIN32
    if (portable)
    {
        if (false == ADM_fileExist(admExecutable(avidemuxVersion)))
        { // Avidemux executable may have been renamed, we support just avidemux{,_cli}_portable.exe
            const char *avidemuxPortableVersion = ui.checkBoxUseQt4->isChecked() ? "avidemux_portable.exe" : "avidemux_cli_portable.exe";
            if (false == ADM_fileExist(admExecutable(avidemuxPortableVersion)))
            {
                ADM_error("Avidemux executable not found in the same directory, skipping job.\n");
                goto done;
            }
            avidemuxVersion = avidemuxPortableVersion;
        }
    }
#endif
    if(false==spawnChild(avidemuxVersion,ScriptFullPath,job.outputFileName))
    {
        ADM_error("Cannot spawn child\n");
        r=false;
        goto done;
    }

    // 3- Wait for connect...
    runSocket=mySocket.waitForConnect(6*1000);
    if(!runSocket)
    {
        ADM_error("No connect\n");
        goto done;
    }
    if(!runSocket->handshake())
    {
        popup("Cannot handshake");
        goto done;
    }
    // 4- wait for complete and/or success message
    
    while(1)
    {
        if(!runSocket->isAlive())
        {
            ADM_info("Exiting loop\n");
            break;
        }
        if(runSocket->pollMessage(msg))
        {
            ADM_info("Got a new message %d\n",msg.command);
            switch(msg.command)
            {
                case ADM_socketCommand_End:
                    if(!msg.getPayloadAsUint32_t(&v))
                    {
                        ADM_error("Cannot read End payload\n");
                        break;
                    }
                    r=(bool)v;
                    ADM_info("Result is %d\n",r);
                    msg.command = ADM_socketCommand_Readback;
                    msg.setPayloadAsUint32_t(v);
                    if(!runSocket->sendMessage(msg))
                        wait+=5000; // avoid starting next job while the application is still running
                    goto done;
                    break;
                case ADM_socketCommand_Progress:
                    if(!msg.getPayloadAsUint32_t(&v))
                    {
                        ADM_error("Cannot read Progress payload\n");
                        break;
                    }
                    printf("Progress %d %%\n",(int)v);
                    dialog->setPercent(v);
                    break;
                default:
                    ADM_error("Unknown command\n");
                    break;
            }
        }
        else
            ADM_usleep(1000*1000); // Refresh once per sec
        QApplication::processEvents();
    }
    ADM_info("** End of slave process **\n");
    ADM_info("** End of slave process **\n");
    ADM_info("** End of slave process **\n");

    // 5-Done, do the cleanup
done:

    if(r) job.status=ADM_JOB_OK;
        else job.status=ADM_JOB_KO;
    job.endTime=ADM_getSecondsSinceEpoch();
    ADMJob::jobUpdate(job);
    if(runSocket) delete runSocket;
    refreshList();
    ADM_info("Running job id = %d\n",job.id);
    ADM_usleep(wait*1000); // Give application some time to exit
    return r;
}
