/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2023 Kim Woelders
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "config.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include "alert.h"
#include "lang.h"
#include "session.h"
#include "util.h"
#if USE_COMPOSITE
#include <X11/extensions/Xcomposite.h>
#endif

void            EUngrabServer(void);

#define ExTextExtents XmbTextExtents
#define ExDrawString XmbDrawString

#define ExSetColor(pxc, r, g, b) \
  do { \
    (pxc)->red = (r << 8) | r; (pxc)->green = (g << 8) | g; (pxc)->blue = (b << 8) | b; \
  } while (0)

static XFontSet xfs = NULL;

#define DRAW_BOX_OUT(mdd, mgc, mwin, mx, my, mw, mh) \
        AlertDrawBox(mdd, mgc, mwin, mx, my, mw, mh, \
        colorful, cols[0], cols[2], cols[3])
#define DRAW_BOX_IN(mdd, mgc, mwin, mx, my, mw, mh) \
        AlertDrawBox(mdd, mgc, mwin, mx, my, mw, mh, \
        colorful, cols[2], cols[0], cols[3])
static void
AlertDrawBox(Display *mdd, GC mgc, Window mwin, int mx, int my, int mw, int mh,
             int colorful, unsigned int c1, unsigned int c2, unsigned int cb)
{
    if (colorful)
    {
        XSetForeground(mdd, mgc, cb);
        XDrawRectangle(mdd, mwin, mgc, mx, my, mw - 1, mh - 1);
        XSetForeground(mdd, mgc, c1);
        XDrawLine(mdd, mwin, mgc, mx + 1, my + 1, mx + mw - 3, my + 1);
        XDrawLine(mdd, mwin, mgc, mx + 1, my + 1, mx + 1, my + mh - 3);
        XSetForeground(mdd, mgc, c2);
        XDrawLine(mdd, mwin, mgc, mx + 2, my + mh - 2, mx + mw - 2,
                  my + mh - 2);
        XDrawLine(mdd, mwin, mgc, mx + mw - 2, my + 2, mx + mw - 2,
                  my + mh - 2);
    }
    else
    {
        XDrawRectangle(mdd, mwin, mgc, mx, my, mw - 1, mh - 1);
    }
}

#define DRAW_THIN_BOX_IN(mdd, mgc, mwin, mx, my, mw, mh) \
        AlertDrawThinBoxIn(mdd, mgc, mwin, mx, my, mw, mh, \
        colorful, cols[2], cols[0])
static void
AlertDrawThinBoxIn(Display *mdd, GC mgc, Window mwin, int mx, int my, int mw,
                   int mh, int colorful, unsigned int c1, unsigned int c2)
{
    if (colorful)
    {
        XSetForeground(mdd, mgc, c1);
        XDrawLine(mdd, mwin, mgc, mx + 1, my + 1, mx + mw - 3, my + 1);
        XDrawLine(mdd, mwin, mgc, mx + 1, my + 1, mx + 1, my + mh - 3);
        XSetForeground(mdd, mgc, c2);
        XDrawLine(mdd, mwin, mgc, mx + 2, my + mh - 2, mx + mw - 2,
                  my + mh - 2);
        XDrawLine(mdd, mwin, mgc, mx + mw - 2, my + 2, mx + mw - 2,
                  my + mh - 2);
    }
}

#define DRAW_HEADER(mdd, mgc, mwin, mx, my, mstr) \
        AlertDrawHeader(mdd, mgc, mwin, mx, my, mstr, \
        colorful, cols[2], cols[3], cols[4])
static void
AlertDrawHeader(Display *mdd, GC mgc, Window mwin, int mx, int my,
                const char *mstr, int colorful, unsigned int cb,
                unsigned int ct1, unsigned int ct2)
{
    int             len = strlen(mstr);

    if (colorful)
    {
        XSetForeground(mdd, mgc, cb);
        ExDrawString(mdd, mwin, xfs, mgc, mx + 1, my + 1, mstr, len);
        ExDrawString(mdd, mwin, xfs, mgc, mx + 2, my + 1, mstr, len);
        ExDrawString(mdd, mwin, xfs, mgc, mx + 2, my + 2, mstr, len);
        ExDrawString(mdd, mwin, xfs, mgc, mx + 1, my + 2, mstr, len);
        XSetForeground(mdd, mgc, ct1);
        ExDrawString(mdd, mwin, xfs, mgc, mx - 1, my, mstr, len);
        ExDrawString(mdd, mwin, xfs, mgc, mx, my - 1, mstr, len);
        ExDrawString(mdd, mwin, xfs, mgc, mx + 1, my, mstr, len);
        ExDrawString(mdd, mwin, xfs, mgc, mx, my + 1, mstr, len);
        XSetForeground(mdd, mgc, ct2);
        ExDrawString(mdd, mwin, xfs, mgc, mx, my, mstr, len);
    }
    else
    {
        ExDrawString(mdd, mwin, xfs, mgc, mx, my, mstr, len);
    }
}

#define DRAW_STRING(mdd, mgc, mwin, mx, my, mstr, len) \
        AlertDrawString(mdd, mgc, mwin, mx, my, mstr, len, colorful, cols[3])
static void
AlertDrawString(Display *mdd, GC mgc, Window mwin, int mx, int my,
                const char *mstr, int len, int colorful, unsigned int ct1)
{
    if (colorful)
    {
        XSetForeground(mdd, mgc, ct1);
        ExDrawString(mdd, mwin, xfs, mgc, mx, my, mstr, len);
    }
    else
    {
        ExDrawString(mdd, mwin, xfs, mgc, mx, my, mstr, len);
    }
}

static char    *
AlertButtonText(int btn, char *buf, unsigned int len, const char *text)
{
    if (!text)
        return NULL;

    Esnprintf(buf, len, "(F%d) %s", btn, text);

    return buf;
}

static void
ShowAlert(const char *title,
          const char *ignore, const char *restart, const char *quit,
          const char *fmt, va_list args)
{
    char            text[4096], buf1[64], buf2[64], buf3[64];
    Window          win, b1 = 0, b2 = 0, b3 = 0, root, btn;
    Display        *dd;
    int             wid, hih, w, h, i, k, mask;
    XGCValues       gcv;
    GC              gc;
    unsigned int    len;
    XEvent          ev;
    XSetWindowAttributes att;
    XRectangle      rect1, rect2;
    char            colorful;
    unsigned long   cols[5];
    XColor          xcl;
    Colormap        cmap;
    int             cnum, fh, x, y, ww, hh, bw, bh, bx;
    char           *str1, *str2, *str3, *p;
    int             button;
    char          **missing_charset_list_return, *def_string_return;
    int             missing_charset_count_return;
    XFontStruct   **font_struct_list_return;
    char          **font_name_list_return;

#if 0
    /* Don't play sound here (maybe if not forked/in signal handler - later) */
    SoundPlay(SOUND_ALERT);
#endif

    EUngrabServer();

    if (!fmt)
        return;

    Evsnprintf(text, sizeof(text), fmt, args);

    /*
     * We may get here from obscure places like an X-error or signal handler
     * and things seem to work properly only if we do a new XOpenDisplay().
     */
    dd = XOpenDisplay(NULL);
    if (!dd)
    {
        fprintf(stderr, "%s\n", text);
        fflush(stderr);
        return;
    }

    button = 0;

    if (!title)
        title = _("Enlightenment Error");
    str1 = AlertButtonText(1, buf1, sizeof(buf1), ignore);
    str2 = AlertButtonText(2, buf2, sizeof(buf2), restart);
    str3 = AlertButtonText(3, buf3, sizeof(buf3), quit);

    cnum = 0;
    colorful = 0;
    cols[0] = cols[1] = cols[2] = cols[3] = cols[4] = 0;
    cmap = DefaultColormap(dd, DefaultScreen(dd));
    if (DefaultDepth(dd, DefaultScreen(dd)) > 4)
    {
        ExSetColor(&xcl, 220, 220, 220);
        if (!XAllocColor(dd, cmap, &xcl))
            goto CN;
        cols[cnum++] = xcl.pixel;
        ExSetColor(&xcl, 160, 160, 160);
        if (!XAllocColor(dd, cmap, &xcl))
            goto CN;
        cols[cnum++] = xcl.pixel;
        ExSetColor(&xcl, 100, 100, 100);
        if (!XAllocColor(dd, cmap, &xcl))
            goto CN;
        cols[cnum++] = xcl.pixel;
        ExSetColor(&xcl, 0, 0, 0);
        if (!XAllocColor(dd, cmap, &xcl))
            goto CN;
        cols[cnum++] = xcl.pixel;
        ExSetColor(&xcl, 255, 255, 255);
        if (!XAllocColor(dd, cmap, &xcl))
            goto CN;
        cols[cnum++] = xcl.pixel;
        colorful = 1;
    }
  CN:

    if (colorful)
        att.background_pixel = cols[1];
    else
        att.background_pixel = BlackPixel(dd, DefaultScreen(dd));
    if (colorful)
        att.border_pixel = cols[3];
    else
        att.border_pixel = WhitePixel(dd, DefaultScreen(dd));
    att.backing_store = Always;
    att.save_under = True;
    att.override_redirect = True;
    mask = CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWSaveUnder |
        CWBackingStore;

#if HAVE_COMPOSITE_OVERLAY_WINDOW
    /*
     * Intended workings:
     * Composite extension not enabled (or COW not available?)
     * - fall back to root
     * Composite extension enabled
     * - use COW whether or not compositing is enabled, window mode too
     */
    root = XCompositeGetOverlayWindow(dd, DefaultRootWindow(dd));
    if (root == None)
#endif
    {
        root = DefaultRootWindow(dd);
    }

    x = y = -100;
    ww = hh = 1;
    win = XCreateWindow(dd, root, x, y, ww, hh, 0,
                        CopyFromParent, InputOutput, CopyFromParent, mask,
                        &att);

    gc = XCreateGC(dd, win, 0, &gcv);
    if (colorful)
        XSetForeground(dd, gc, cols[3]);
    else
        XSetForeground(dd, gc, att.border_pixel);

    xfs = XCreateFontSet(dd, "fixed",
                         &missing_charset_list_return,
                         &missing_charset_count_return, &def_string_return);
    if (!xfs)
        goto done;

    if (missing_charset_list_return)
        XFreeStringList(missing_charset_list_return);

    k = XFontsOfFontSet(xfs, &font_struct_list_return, &font_name_list_return);
    fh = 0;
    for (i = 0; i < k; i++)
    {
        h = font_struct_list_return[i]->ascent +
            font_struct_list_return[i]->descent;
        if (fh < h)
            fh = h;
    }

    XSelectInput(dd, win, ExposureMask);
    XMapWindow(dd, win);

    XGrabServer(dd);

    XGrabPointer(dd, win, False, ButtonPressMask | ButtonReleaseMask,
                 GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
    XGrabKeyboard(dd, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
    XGrabKey(dd, AnyKey, AnyModifier, win, False, GrabModeAsync, GrabModeAsync);

    XSetInputFocus(dd, win, RevertToPointerRoot, CurrentTime);

    XSync(dd, False);

    wid = DisplayWidth(dd, DefaultScreen(dd));
    hih = DisplayHeight(dd, DefaultScreen(dd));
    ww = (wid >= 600) ? 600 : (wid / 40) * 40;
    hh = (hih >= 440) ? 440 : (hih / 40) * 40;

    for (i = 40; i < ww; i += 40)
    {
        w = i;
        h = (i * hh) / ww;
        x = (wid - w) >> 1;
        y = (hih - h) >> 1;
        XMoveResizeWindow(dd, win, x, y, w, h);
        DRAW_BOX_OUT(dd, gc, win, 0, 0, w, h);
        XSync(dd, False);
        SleepUs(20000);
    }
    x = (wid - ww) >> 1;
    y = (hih - hh) >> 1;
    XMoveResizeWindow(dd, win, x, y, ww, hh);
    XSync(dd, False);

    bw = 0;
    if (str1)
    {
        ExTextExtents(xfs, str1, strlen(str1), &rect1, &rect2);
        bw = (rect2.width > bw) ? rect2.width : bw;
    }
    if (str2)
    {
        ExTextExtents(xfs, str2, strlen(str2), &rect1, &rect2);
        bw = (rect2.width > bw) ? rect2.width : bw;
    }
    if (str3)
    {
        ExTextExtents(xfs, str3, strlen(str3), &rect1, &rect2);
        bw = (rect2.width > bw) ? rect2.width : bw;
    }
    bw += 20;
    bh = fh + 10;

#define BX(i) (5 + (((ww - bw - 10) * (i)) / 2))
#define BY    (hh - bh - 5)

    if (str1)
    {
        b1 = XCreateWindow(dd, win, BX(0), BY, bw, bh, 0, CopyFromParent,
                           InputOutput, CopyFromParent, mask, &att);
        XMapWindow(dd, b1);
    }
    if (str2)
    {
        b2 = XCreateWindow(dd, win, BX(1), BY, bw, bh, 0, CopyFromParent,
                           InputOutput, CopyFromParent, mask, &att);
        XMapWindow(dd, b2);
    }
    if (str3)
    {
        b3 = XCreateWindow(dd, win, BX(2), BY, bw, bh, 0, CopyFromParent,
                           InputOutput, CopyFromParent, mask, &att);
        XMapWindow(dd, b3);
    }
    XSync(dd, False);

    button = 0;
    for (; button == 0;)
    {
        XNextEvent(dd, &ev);

        switch (ev.type)
        {
        case KeyPress:
            if (str1 && ev.xkey.keycode == XKeysymToKeycode(dd, XK_F1))
            {
                button = 1;
                btn = b1;
                goto do_KeyPress;
            }
            if (str2 && ev.xkey.keycode == XKeysymToKeycode(dd, XK_F2))
            {
                button = 2;
                btn = b2;
                goto do_KeyPress;
            }
            if (str3 && ev.xkey.keycode == XKeysymToKeycode(dd, XK_F3))
            {
                button = 3;
                btn = b3;
                goto do_KeyPress;
            }
            break;
          do_KeyPress:
            DRAW_BOX_IN(dd, gc, btn, 0, 0, bw, bh);
            XSync(dd, False);
            SleepUs(500000);
            DRAW_BOX_OUT(dd, gc, btn, 0, 0, bw, bh);
            goto do_sync;

        case ButtonPress:
            if (!(ev.xbutton.y >= BY && ev.xbutton.y < BY + bh))
                break;

            bx = BX(0);
            if (b1 && ev.xbutton.x >= bx && ev.xbutton.x < bx + bw)
            {
                btn = b1;
                goto do_ButtonPress;
            }
            bx = BX(1);
            if (b2 && ev.xbutton.x >= bx && ev.xbutton.x < bx + bw)
            {
                btn = b2;
                goto do_ButtonPress;
            }
            bx = BX(2);
            if (b3 && ev.xbutton.x >= bx && ev.xbutton.x < bx + bw)
            {
                btn = b3;
                goto do_ButtonPress;
            }
            break;
          do_ButtonPress:
            DRAW_BOX_IN(dd, gc, btn, 0, 0, bw, bh);
            goto do_sync;

        case ButtonRelease:
            if (!(ev.xbutton.y >= BY && ev.xbutton.y < BY + bh))
                break;

            bx = BX(0);
            if (b1 && ev.xbutton.x >= bx && ev.xbutton.x < bx + bw)
            {
                button = 1;
                btn = b1;
                goto do_ButtonRelease;
            }
            bx = BX(1);
            if (b2 && ev.xbutton.x >= bx && ev.xbutton.x < bx + bw)
            {
                button = 2;
                btn = b2;
                goto do_ButtonRelease;
            }
            bx = BX(2);
            if (b3 && ev.xbutton.x >= bx && ev.xbutton.x < bx + bw)
            {
                button = 3;
                btn = b3;
                goto do_ButtonRelease;
            }
            break;
          do_ButtonRelease:
            DRAW_BOX_OUT(dd, gc, btn, 0, 0, bw, bh);
            goto do_sync;

        case Expose:
            /* Flush all other Expose events */
            while (XCheckTypedWindowEvent(dd, ev.xexpose.window, Expose, &ev))
                ;

            ExTextExtents(xfs, title, strlen(title), &rect1, &rect2);
            w = rect2.width;

            DRAW_HEADER(dd, gc, win, (ww - w) / 2, 5 - rect2.y, title);
            DRAW_BOX_OUT(dd, gc, win, 0, 0, ww, bh);
            DRAW_BOX_OUT(dd, gc, win, 0, bh - 1, ww, hh - fh - fh - 30 + 2);
            DRAW_BOX_OUT(dd, gc, win, 0, hh - fh - 20, ww, fh + 20);
            k = bh;
            for (p = text;; p += len + 1)
            {
                len = strcspn(p, "\n");
                DRAW_STRING(dd, gc, win, 6, 6 + k + fh, p, len);
                k += fh + 2;
                if (p[len] == '\0')
                    break;
            }
            if (str1)
            {
                ExTextExtents(xfs, str1, strlen(str1), &rect1, &rect2);
                w = rect2.width;
                DRAW_HEADER(dd, gc, b1, (bw - w) / 2, 5 - rect2.y, str1);
                DRAW_BOX_OUT(dd, gc, b1, 0, 0, bw, bh);
                DRAW_THIN_BOX_IN(dd, gc, win,
                                 BX(0) - 2, BY - 2, bw + 4, bh + 4);
            }
            if (str2)
            {
                ExTextExtents(xfs, str2, strlen(str2), &rect1, &rect2);
                w = rect2.width;
                DRAW_HEADER(dd, gc, b2, (bw - w) / 2, 5 - rect2.y, str2);
                DRAW_BOX_OUT(dd, gc, b2, 0, 0, bw, bh);
                DRAW_THIN_BOX_IN(dd, gc, win,
                                 BX(1) - 2, BY - 2, bw + 4, bh + 4);
            }
            if (str3)
            {
                ExTextExtents(xfs, str3, strlen(str3), &rect1, &rect2);
                w = rect2.width;
                DRAW_HEADER(dd, gc, b3, (bw - w) / 2, 5 - rect2.y, str3);
                DRAW_BOX_OUT(dd, gc, b3, 0, 0, bw, bh);
                DRAW_THIN_BOX_IN(dd, gc, win,
                                 BX(2) - 2, BY - 2, bw + 4, bh + 4);
            }
          do_sync:
            XSync(dd, False);
            SleepUs(200000);
            break;

        default:
            break;
        }
    }

    XFreeFontSet(dd, xfs);
  done:
    XUngrabServer(dd);
#if HAVE_COMPOSITE_OVERLAY_WINDOW
    /* Force damage on root window where GSOD is/was rendered */
    if (root != DefaultRootWindow(dd))
    {
        XReparentWindow(dd, win, DefaultRootWindow(dd), x, y);
        XUnmapWindow(dd, win);
        XSync(dd, False);
        SleepUs(20000);
    }
#endif
    XDestroyWindow(dd, win);
    XFreeGC(dd, gc);
    if (cnum > 0)
        XFreeColors(dd, cmap, cols, cnum, 0);
    XCloseDisplay(dd);

    switch (button)
    {
    default:
    case 1:
        break;
    case 2:
        SessionExit(EEXIT_RESTART, NULL);
        break;
    case 3:
        SessionExit(EEXIT_EXIT, NULL);
        break;
    }
}

void
AlertX(const char *title, const char *ignore,
       const char *restart, const char *quit, const char *fmt, ...)
{
    va_list         args;

    va_start(args, fmt);
    ShowAlert(title, ignore, restart, quit, fmt, args);
    va_end(args);
}

void
Alert(const char *fmt, ...)
{
    va_list         args;

    va_start(args, fmt);
    ShowAlert(_("Enlightenment Message Dialog"), _("Ignore this"),
              _("Restart Enlightenment"), _("Quit Enlightenment"), fmt, args);
    va_end(args);
}

void
AlertOK(const char *fmt, ...)
{
    va_list         args;

    va_start(args, fmt);
    ShowAlert(_("Attention !!!"), _("OK"), NULL, NULL, fmt, args);
    va_end(args);
}
