/*
 * (C) Copyright 2008 Openmoko Inc.
 * Author: Holger Hans Peter Freyther
 *
 * Licensed under GPLv2 or later, no warranty
 *
 * keep a giving process going
 *
 * stolen and inspired from e_alert.c
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <X11/Xlib.h>
#include <X11/X.h>

static void usage(char* name)
{
    fprintf(stderr, "%s crash_comment appname [params for app]\n", name);
}

/*
 * handle a crash. Open a display connection... create windows...
 * ...
 */
static void handle_crash(int status, const char* question)
{
    Display* display;
    Font font;
    XFontStruct *fs = NULL;
    GC gc = 0;
    Window  win = 0, b1 = 0, b2 = 0;
    char *title, *button1, *button2;
    int wid, hih, mask, fw, fh, mh, button;
    int ww = 320, hh = 240;
    char line[1024];
    int w, i, j, k;
    XSetWindowAttributes att;
    XGCValues gcv;
    XEvent ev;
    KeyCode              key;

    /* create the user interface */
    display = XOpenDisplay(NULL);
    if (!display) {
        fprintf(stderr, "Opening the X display failed\n");
        exit(-1);
    }

    font = XLoadFont(display, "fixed");
    fs = XQueryFont(display, font);
    if (!fs) {
        fprintf(stderr, "XQueryFont failed\n");
        exit(-1);
    }

    title = "Application Error";
    button1 = "(F1) Restart";
    button2 = "(F2) Exit";

    wid = DisplayWidth(display, DefaultScreen(display));
    hih = DisplayHeight(display, DefaultScreen(display));
    

    att.background_pixel = WhitePixel(display, DefaultScreen(display));
    att.border_pixel     = BlackPixel(display, DefaultScreen(display));
    att.override_redirect = True;
    mask = CWBackPixel | CWBorderPixel | CWOverrideRedirect;

    win = XCreateWindow(display, DefaultRootWindow(display), 
                        (wid - ww) / 2, (hih - hh) / 2, ww, hh, 0,
                        CopyFromParent, InputOutput,
                        CopyFromParent, mask, &att);

    b1 = XCreateWindow(display, win, -100, -100, 1, 1, 0, CopyFromParent,
                       InputOutput, CopyFromParent, mask, &att);
    b2 = XCreateWindow(display, win, -100, -100, 1, 1, 0, CopyFromParent,
                       InputOutput, CopyFromParent, mask, &att);
    XMapWindow(display, b1);
    XMapWindow(display, b2);

    gc = XCreateGC(display, win, 0, &gcv);
    XSetForeground(display, gc, att.border_pixel);
    XSelectInput(display, win, KeyPressMask | KeyReleaseMask | ExposureMask);

    /* place the dialog */
    XMapWindow(display, win);


    /* handle the events */
    fh = fs->ascent + fs->descent;
    mh = ((ww - 20) / 2) - 20;

    /* fixed size... */
    w = 20;
    XMoveResizeWindow(display, b1, w, hh - 15 - fh, mh + 10, fh + 10);
    XSelectInput(display, b1, ButtonPressMask | ButtonReleaseMask | ExposureMask);
    w = ww - 20 - mh;
    XMoveResizeWindow(display, b2, w, hh - 15 - fh, mh + 10, fh + 10);
    XSelectInput(display, b2, ButtonPressMask | ButtonReleaseMask | ExposureMask);

    XMapWindow(display, win);
    XGrabPointer(display, win, True, ButtonPressMask | ButtonReleaseMask,
            GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
    XGrabKeyboard(display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
    XSetInputFocus(display, win, RevertToPointerRoot, CurrentTime);
    XSync(display, False);

    button = 0;
    for (; button == 0;) {
        XNextEvent(display, &ev);
        switch (ev.type) {
        case KeyPress:
            key = XKeysymToKeycode(display, XStringToKeysym("F1"));
            if (key == ev.xkey.keycode) {
                button = 1;
                break;
            }
            key = XKeysymToKeycode(display, XStringToKeysym("F2"));
            if (key == ev.xkey.keycode) {
                button = 2;
                break;
            }
            break;
        case ButtonPress:
            if (ev.xbutton.window == b1)
                button = 1;
            else if (ev.xbutton.window == b2)
                button = 2;
            break;
        case Expose:
            while (XCheckTypedWindowEvent(display, ev.xexpose.window, Expose, &ev));

            /* outline */
            XDrawRectangle(display, win, gc, 0, 0, ww - 1, hh - 1);

            XDrawRectangle(display, win, gc, 2, 2, ww - 4 - 1, fh + 4 - 1);

            fw = XTextWidth(fs, title, strlen(title));
            XDrawString(display, win, gc, 2 + 2 + ((ww - 4 - 4 - fw) / 2) , 2 + 2 + fs->ascent, title, strlen(title));

            i = 0;
            j = 0;
            k = 2 + fh + 4 + 2;
            while (question[i]) {
                line[j++] = question[i++];
                if (line[j - 1] == '\n') {
                    line[j - 1] = 0;
                    j = 0;
                    XDrawString(display, win, gc, 4, k + fs->ascent, line, strlen(line));
                    k += fh + 2;
                }
            }
            fw = XTextWidth(fs, button2, strlen(button1));
            XDrawRectangle(display, b1, gc, 0, 0, mh - 1, fh + 10 - 1);
            XDrawString(display, b1, gc, 5 + ((mh - fw) / 2), 5 + fs->ascent, button1, strlen(button1));

            fw = XTextWidth(fs, button2, strlen(button2));
            XDrawRectangle(display, b2, gc, 0, 0, mh - 1, fh + 10 - 1);
            XDrawString(display, b2, gc, 5 + ((mh - fw) / 2), 5 + fs->ascent, button2, strlen(button2));

            XSync(display, False);
            break;

        default:
            break;
        }
    }


    /* destruct the X connection and give up the resources */
    XDestroyWindow(display, win);
    XFreeGC(display, gc);
    XFreeFont(display, fs);
    XSync(display, False);
    XCloseDisplay(display);

    switch (button) {
    case 2:
        fprintf(stderr, "Was asked to quit. Bye Bye\n");
        exit(-11);
    case 1:
    default:
        break;
    }
}


/*
 * Fork and execute the command, wait for the command to finish.
 * On any kind of exit ask the user to restart
 */
int fork_and_exec(int argc, char** argv)
{
    pid_t pid = vfork();

    if (pid == 0) {
        execvp(argv[0], argv);
        fprintf(stderr, "Failed to launch: %s\n", argv[0]);
        _exit(-1);
    } else {
        int status;

        fprintf(stderr, "Waiting for pid: %d\n", pid);

        /* wait for the process to die */
        while (waitpid(pid, &status, 0) != pid);

        fprintf(stderr, "The process exited: %d normal: %d\n", status, WIFEXITED(status));
        return status;
    }

    assert(0);
}

int main(int argc, char** argv)
{
    int i = 0;

    if (argc < 3) {
        usage(argv[0]);
        return EXIT_FAILURE;
    }

    if (strlen(argv[1]) >= 1024) {
        fprintf(stderr, "The comment string has to be less than 1024 bytes\n");
        return EXIT_FAILURE;
    }

    /* Create a list for execvp with a sentinel */
    int commands = argc-2;
    char** command_arguments = (char**) malloc(sizeof(char*)*(commands+1));
    for (i = 0; i < commands; ++i)
        command_arguments[i] = argv[2+i];
    command_arguments[commands] = NULL;

    for (;;) {
        int status = fork_and_exec(commands, command_arguments);
        handle_crash(status, argv[1]);
    }
}
