// ========================================================= //
//                                                           //
//   File      : xcmd.cxx                                    //
//   Purpose   : support to run commands in (x)terminal      //
//                                                           //
//   Coded by Ralf Westram (coder@reallysoft.de) in Sep 25   //
//   http://www.arb-home.de/                                 //
//                                                           //
// ========================================================= //

#include "xcmd.hxx"
#include "dbserver.hxx"

#include <arbdb.h>
#include <arb_strbuf.h>
#include <arb_sleep.h>
#include <aw_msg.hxx>

using namespace std;

// ---------------------------
//      external commands

#define SYNC_STATE_STARTED  "started"
#define SYNC_STATE_FINISHED "finished"

static GB_ERROR wait_for_sync__servingDB(GBDATA *gb_main, const char *sync_id) {
    bool     sync_reached = false;
    GB_ERROR error        = NULp;

    ARB_timeout show_wait(2, SEC);
    ARB_timeout show_serve_error(500, MS);

    while (!sync_reached && !error) {
        GB_ERROR serve_error = serve_db_while_GUI_is_blocked(gb_main);
        if (serve_error) {
            if (show_serve_error.passed()) {
                // Note: this error is unexpected and might indicate a severe problem (deadlock, broken design, ...)
                fprintf(stderr, "Error serving DB (while waiting for sync '%s'): %s\n", sync_id, serve_error);
                show_serve_error.restart(1.7);
            }
        }

        GB_transaction ta(gb_main);
        GB_CSTR value = GB_read_sync_value(gb_main, sync_id);
        if (value) {
            if (strcmp(value, SYNC_STATE_FINISHED) == 0) {
                fprintf(stderr, "reached expected sync on ID '%s': current value is '%s'\n", sync_id, value);
                sync_reached = true;
            }
            else if (strcmp(value, SYNC_STATE_STARTED) != 0) {
                error = GBS_global_string("sync-ID '%s' has unexpected value '%s'", sync_id, value);
            }
            else {
                if (show_wait.passed()) {
                    fprintf(stderr, "waiting for sync on ID '%s': current value is '%s'\n", sync_id, value);
                    show_wait.restart(1.7);
                }
            }
        }
        else {
            error = GB_await_error();
        }
    }

    return error;
}

GB_ERROR ARB_system(const char *cmd, XCmdType boundExectype) {
    XCMD_TYPE  exectype = boundExectype.get_type();
    GBDATA    *gb_main  = boundExectype.get_gb_main();

    // runs a command in an xterm

    bool  background         = exectype & _XCMD__ASYNC;     // true -> run asynchronous
    bool  requires_DB_access = exectype & _XCMD__ACCESS_DB; // true -> assume shell process requires DB access
    bool  always_wait_key    = exectype & _XCMD__WAITKEY;   // true -> always wait for keypress (otherwise only wait if 'cmd' fails)
    bool  hidden             = exectype & _XCMD__HIDDEN;    // true -> do not use terminal window
    char *sync_via_ID        = NULp;

    if (!background && requires_DB_access) {
        if (GB_is_server(gb_main)) {
            static int sync_counter = 0;
            sync_via_ID             = GBS_global_string_copy("xcmd%i", ++sync_counter);

            // run shell command asynchronously (synchronisation will be done via database)
            background = true;

            fprintf(stderr, "synchronise command using ID '%s'. command=%s\n", sync_via_ID, cmd);
        }
        // else: we are a client -> database access is possible from synchronous shell command
        // (Note: this might fail, if the client itself currently blocks the server)
    }

    const int     BUFSIZE = 4096;
    GBS_strstruct system_call(BUFSIZE);

    system_call.put('(');
    if (!hidden) system_call.cat(GB_getenvARB_XCMD());

    {
        GBS_strstruct bash_command(BUFSIZE);

        GB_CSTR ldlibpath = GB_getenv("LD_LIBRARY_PATH"); // due to SIP ldlibpath is always NULL under osx
                                                          // (should not matter because all binaries are linked statically)

        if (ldlibpath) {
            bash_command.cat("LD_LIBRARY_PATH=");
            {
                char *dquoted_library_path = GBK_doublequote(ldlibpath);
                bash_command.cat(dquoted_library_path);
                free(dquoted_library_path);
            }
            bash_command.cat(";export LD_LIBRARY_PATH;");
        }
#if !defined(DARWIN)
        else {
            // under Linux it normally is a problem if LD_LIBRARY_PATH is undefined
            // -> warn in terminal to provide a hint:
            bash_command.cat("echo 'Warning: LD_LIBRARY_PATH is undefined';");
        }
#endif

        bash_command.cat(" (");
        bash_command.cat(cmd);

        {
            char *sync_command        = sync_via_ID ? GBS_global_string_copy("arb_sync --write %s " SYNC_STATE_FINISHED, sync_via_ID) : NULp;
            const char *wait_commands = hidden ? "sleep 1" : "echo; echo 'Press ENTER to close this window'; read a";

            if (always_wait_key) {
                bash_command.cat("; [ $? -ne 0 ] && echo \"Command terminated with failure (exit code $?)\""); // display failure
                bash_command.cat("; ");
            }
            else {
                bash_command.cat("; xc=$?");  // store exitcode of command
                if (sync_command) {
                    bash_command.cat("; ");
                    bash_command.cat(sync_command); // sync arb macro execution
                }
                bash_command.cat("; [ $xc -ne 0 ] && echo \"Command terminated with failure (exit code $xc)\""); // display failure
                bash_command.cat("; exit $xc"); // restore exitcode of command
                bash_command.cat(") || (");
            }
            bash_command.cat(wait_commands);
            if (always_wait_key && sync_command) {
                bash_command.cat("; ");
                bash_command.cat(sync_command); // sync arb macro execution
            }
            bash_command.put(')');

            free(sync_command);
        }

        char *squoted_bash_command = GBK_singlequote(bash_command.get_data());
        system_call.cat(" bash -c ");
        system_call.cat(squoted_bash_command);
        free(squoted_bash_command);
    }
    system_call.cat(" )");
    if (background) system_call.cat(" &");

    GB_ERROR error = NULp;
    if (sync_via_ID) {
        GB_transaction ta(gb_main);
        error = GB_write_sync_value(gb_main, sync_via_ID, SYNC_STATE_STARTED);
    }

    if (!error) {
        error = GBK_system(system_call.get_data());
        // if error occurs here (or before) -> sync value will never change
    }

    if (sync_via_ID && !error) {
        error = wait_for_sync__servingDB(gb_main, sync_via_ID);
    }

    freenull(sync_via_ID);

    return error;
}

void ARB_system_in_console_cb(AW_window*, const char *command, const XCmdType *exectype) {
    // like ARB_system, but usable as WindowCallback + displays errors as message.
    aw_message_if(ARB_system(command, *exectype));
}

