// ========================================================= //
//                                                           //
//   File      : MacroExitor.hxx                             //
//   Purpose   : Safe program exit via macro                 //
//                                                           //
//   Coded by Ralf Westram (coder@reallysoft.de) in Nov 25   //
//   http://www.arb-home.de/                                 //
//                                                           //
// ========================================================= //

#ifndef AW_ROOT_HXX
#include <aw_root.hxx>
#endif
#ifndef AW_MSG_HXX
#include <aw_msg.hxx>
#endif
#ifndef AW_QUESTION_HXX
#include <aw_question.hxx>
#endif
#ifndef ARB_SLEEP_H
#include <arb_sleep.h>
#endif

#ifndef MACROEXITOR_HXX
#define MACROEXITOR_HXX

#define TRY_TO_QUIT_ARB_FREQUENCY 250 // ms

class MacroExitor : virtual Noncopyable {
    AW_root    *aw_root;
    bool        suppressed; // true after first exit attempt
    const char *quitWhat; // name of application to quit (e.g. "arb")

    __ATTR__NORETURN virtual void perform_exit()  = 0; // perform_exit() may NOT return.
    virtual GBDATA *get_gbmain_checked_for_save() = 0; // shall return database which will be checked for saving.

    bool need_to_delay_exit() const { return is_executing_macro_as_server(aw_root); }

    __ATTR__NORETURN void perform_exit_or_terminate() {
        perform_exit();
        GBK_terminate("The exit mechanism didn't work, but resistance is futile.");
    }

    unsigned exit_delayed() {
        if (need_to_delay_exit()) {
            // use one global timeout (exitors might be nested, but exit only happens once)
            static long milliSeconds    = 0;
            static int  lastDecaSeconds = 0;

            milliSeconds    += TRY_TO_QUIT_ARB_FREQUENCY;
            int seconds      = milliSeconds/1000;
            int decaSeconds  = seconds/10;

            if (decaSeconds != lastDecaSeconds) {
                const char *msg = GBS_global_string("waited %i seconds for macro termination", seconds);
                if (decaSeconds>3) {
                    int after = (10-decaSeconds)*10;
                    msg       = GBS_global_string("%s. Will terminate unconditionally in %i seconds", msg, after);
                }
                aw_message(GBS_global_string("[%s]", msg));
                lastDecaSeconds = decaSeconds;
            }

            if (decaSeconds<10) {
                return TRY_TO_QUIT_ARB_FREQUENCY;
            }
            // reached after 100s
            aw_message(GBS_global_string("[forcing %s quit]", quitWhat));
        }
        else {
            aw_message(GBS_global_string("[all macros have terminated. %s quits now]", quitWhat));
        }
        ARB_sleep(1, SEC);
        perform_exit_or_terminate();
        return 0; // never reached
    }
    static unsigned exit_delayed(AW_root*, MacroExitor *exitor) { return exitor->exit_delayed(); }

    static bool user_does_confirm_quit__if_server(GBDATA *gb_main, const char *appName) {
        bool shallQuit = true;

        if (gb_main && // we have a database (e.g. have none intro window)
            GB_read_clients(gb_main) >= 0 && // only ask, if arb is server.
            GB_read_clock(gb_main) > GB_last_saved_clock(gb_main))
        {
            long  secs         = GB_last_saved_time(gb_main);
            char *quit_buttons = GBS_global_string_copy("Quit %s,Do NOT quit", appName);
            char *question     = NULp;
            if (secs) {
                secs = GB_time_of_day() - secs;
                if (secs>15) {
                    question = GBS_global_string_copy("You last saved your data %li:%02li minutes ago\nSure to quit?", secs/60, secs%60);
                }
            }
            else {
                question = ARB_strdup("You never saved any data.\nSure to quit?");
            }

#if defined(DEBUG)
            if (question) {
                freeset(question,     GBS_string_eval(question,     ":\n=;"));
                freeset(quit_buttons, GBS_string_eval(quit_buttons, ":\n=;"));
                fprintf(stderr, "[NDEBUG version would query user with: '%s' -> '%s']\n", question, quit_buttons);
            }
            else {
                fprintf(stderr, "[NDEBUG version would also quit %s w/o query]\n", appName);
            }
            freenull(question);
#endif

            if (question) {
                shallQuit = aw_question("quit_arb", question, quit_buttons) == 0;
                free(question);
            }
            free(quit_buttons);
        }
        return shallQuit;
    }

public:
    MacroExitor(AW_root *aw_root_, const char *appName)
        : aw_root(aw_root_),
          suppressed(false),
          quitWhat(appName)
    {}
    virtual ~MacroExitor() {}

    void maybe_exit_delayed() {
        // Normally this method exits the program.
        //
        // When a macro is running, this method will return instantly.
        // The program waits until the macro terminates, and then exits the program
        // (using perform_exit() from the derived class).
        //
        // Otherwise, if no macro is running, the user will be asked whether he is sure to quit.
        // If the database runs as client, this question will be auto-confirmed, because some server is still running.
        //
        // When confirmed, the program instantly exits.
        // Otherwise the method returns.
        //
        // The two cases if which the method returns can be distinguished using the
        // result of is_performing_delayed_exit():
        // - if true, the program will automatically exit (delayed).
        // - if false, the user did not confirm to quit. In this case 'this' should be deleted,
        //   to avoid inconsistent behavior.

        if (need_to_delay_exit()) {
            if (!suppressed) {
                aw_message(GBS_global_string("[did not quit %s yet, because macro is still running. Would crash. Will retry ASAP]", quitWhat));
                suppressed = true;
            }
            else {
                aw_message("[macro is still running. But time will pass faster because you are in a hurry]");
            }

            aw_root->add_timed_callback(TRY_TO_QUIT_ARB_FREQUENCY,
                                        makeTimedCallback(MacroExitor::exit_delayed, this));
        }
        else {
            // if control flow comes here,
            // - this app is either running as CLIENT (for the database used for macro execution), or
            // - it is a SERVER, and no macro is running (in that case the if branch above is chosen).
            //
            // Note:
            // - CLIENTS always quit w/o question
            // - SERVERS do this only during development,
            //   otherwise the user is asked if he is sure to quit.

            if (user_does_confirm_quit__if_server(get_gbmain_checked_for_save(), quitWhat)) {
                perform_exit_or_terminate();
            }
        }
    }

    AW_root *get_root() const { return aw_root; }
    bool is_performing_delayed_exit() const { return suppressed; }
};


#else
#error MacroExitor.hxx included twice
#endif // MACROEXITOR_HXX
