/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2007-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"
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xproto.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#ifdef USE_XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/keysym.h>
#include "dox.h"

#define EDOX_DEFAULT_W  512
#define EDOX_DEFAULT_H  400

Display        *disp;
Root            VRoot;

static Window   win_main = None, win_text = None;
static Window   win_title = None, win_exit = None;
static Window   win_next = None, win_prev = None;

static Imlib_Image im_title = NULL;
static Imlib_Image im_prev1 = NULL, im_prev2 = NULL;
static Imlib_Image im_next1 = NULL, im_next2 = NULL;
static Imlib_Image im_exit1 = NULL, im_exit2 = NULL;

static const char doxdir[] = ENLIGHTENMENT_ROOT "/E-docs";
char           *docdir = NULL;

static Atom     ATOM_WM_DELETE_WINDOW = None;
static Atom     ATOM_WM_PROTOCOLS = None;

static char     show_top_bar = 1;

static          Window
FindRootWindow(Display *dpy)
{
    Window          win1, win2, win3, root_win;
    char           *str;
    Atom            a, at;
    int             format_ret;
    unsigned long   bytes_after, num_ret;
    unsigned long  *retval;

    root_win = DefaultRootWindow(dpy);

    str = getenv("ENL_WM_ROOT");
    if (!str)
        goto done;
    win1 = strtoul(str, NULL, 0);

    a = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", True);

    XGetWindowProperty(dpy, win1, a, 0, 1, False, XA_WINDOW, &at,
                       &format_ret, &num_ret, &bytes_after,
                       (unsigned char **)&retval);
    if (!retval)
        goto done;
    win2 = retval[0];
    XFree(retval);

    XGetWindowProperty(dpy, win2, a, 0, 1, False, XA_WINDOW, &at,
                       &format_ret, &num_ret, &bytes_after,
                       (unsigned char **)&retval);
    if (!retval)
        goto done;
    win3 = retval[0];
    XFree(retval);

    if (win2 != win3)
        goto done;

    root_win = win1;

  done:
    return root_win;
}

static void
VRootInit(void)
{
    Window          root_return;
    int             x_return, y_return;
    unsigned int    border_width_return;

    VRoot.scr = DefaultScreen(disp);
    VRoot.win = FindRootWindow(disp);
    XGetGeometry(disp, VRoot.win, &root_return, &x_return, &y_return,
                 &VRoot.w, &VRoot.h, &border_width_return, &VRoot.depth);
    VRoot.vis = DefaultVisual(disp, VRoot.scr);
    VRoot.depth = DefaultDepth(disp, VRoot.scr);
    VRoot.cmap = DefaultColormap(disp, VRoot.scr);

    imlib_set_color_usage(128);

    imlib_context_set_display(disp);
    imlib_context_set_visual(VRoot.vis);
    imlib_context_set_colormap(VRoot.cmap);
    imlib_context_set_dither(1);
    imlib_context_set_dither_mask(0);
}

static          Window
CreateWindow(Window parent, int x, int y, int ww, int hh)
{
    Window          win;
    XSetWindowAttributes attr;
    XSizeHints      hnt;

    attr.backing_store = NotUseful;
    attr.override_redirect = False;
    attr.colormap = VRoot.cmap;
    attr.border_pixel = 0;
    attr.background_pixel = 0;
    attr.save_under = False;
    win = XCreateWindow(disp, parent, x, y, ww, hh, 0, VRoot.depth,
                        InputOutput, VRoot.vis,
                        CWOverrideRedirect | CWSaveUnder | CWBackingStore |
                        CWColormap | CWBackPixel | CWBorderPixel, &attr);
    XSetWindowBackground(disp, win, 0);
    XStoreName(disp, win, "DOX: Enlightenment Document Viewer");
    hnt.flags = USPosition | USSize | PPosition | PSize | PMinSize | PMaxSize;
    hnt.x = x;
    hnt.y = y;
    hnt.width = ww;
    hnt.height = hh;
    hnt.min_width = ww;
    hnt.max_width = ww;
    hnt.min_height = hh;
    hnt.max_height = hh;
    XSetWMNormalHints(disp, win, &hnt);

    ATOM_WM_PROTOCOLS = XInternAtom(disp, "WM_PROTOCOLS", False);
    ATOM_WM_DELETE_WINDOW = XInternAtom(disp, "WM_DELETE_WINDOW", False);
    XSetWMProtocols(disp, win, &ATOM_WM_DELETE_WINDOW, 1);

    return win;
}

static          Imlib_Image
ImageLoad(const char *dir, const char *file)
{
    char            tmp[4096];

    Esnprintf(tmp, sizeof(tmp), "%s/%s", dir, file);
    if (!exists(tmp))
        return NULL;

    return imlib_load_image(tmp);
}

static          Imlib_Image
ImageLoadDox(const char *file)
{
    return ImageLoad(doxdir, file);
}

Imlib_Image
ImageLoadDoc(const char *file)
{
    Imlib_Image     im;

    im = ImageLoad(docdir, file);
    if (im)
        return im;

    im = ImageLoadDox(file);

    return im;
}

static void
ApplyImage1(Window win, Imlib_Image im)
{
    Pixmap          pmap = 0, mask = 0;

    imlib_context_set_image(im);
    imlib_context_set_drawable(win);
    imlib_render_pixmaps_for_whole_image(&pmap, &mask);
    XSetWindowBackgroundPixmap(disp, win, pmap);
    imlib_free_pixmap_and_mask(pmap);
}

static void
ApplyImage2(Window win, Imlib_Image im)
{
    imlib_context_set_image(im);
    imlib_context_set_drawable(win);
    imlib_render_image_on_drawable(0, 0);
}

static void
LoadFile(const char *file, const char *docfile)
{
    char            s[4096];
    FILE           *f;

    f = fopen(file, "r");
    if (!f)
    {
        strcpy(s, docdir);
        strcat(s, "/");
        strcat(s, docfile);
        f = fopen(s, "r");
        if (!f)
        {
            printf("Edoc_dir %s does not contain a %s file\n", docdir, docfile);
            exit(1);
        }
    }

    GetObjects(f);
    fclose(f);
}

static void
usage(const char *prog)
{
    printf("usage:\n"
           "%s [-page page_number] [-file Edoc_fname] [-size width height] [Edoc_dir]\n",
           prog);
    exit(1);
}

#define ApplyImage3(win, im) \
    XClearWindow(disp, win)

#define FREE_LINKS \
ll = l; \
while (ll) \
{ \
  l = ll; \
  ll = ll->next; \
  Efree(l->name); \
  Efree(l); \
}

#define UPDATE_NOW \
{ \
XSetWindowBackgroundPixmap(disp, win_text, draw); \
XClearWindow(disp, win_text); \
}

#define UPDATE \
{ \
  int up_i, up_j; \
  int up_lut[16] = { 0, 8, 4, 12, 2, 6, 10, 14, \
                     3, 11, 1, 9, 7, 13, 5, 15}; \
  XSetWindowBackgroundPixmap(disp, win_text, draw); \
  for (up_j = 0; up_j < 16; up_j++) \
    { \
      for (up_i = 0; up_i < h; up_i += 16) \
        { \
          XClearArea(disp, win_text, 0, up_i + up_lut[up_j], w, 1, False); \
        } \
      XSync(disp, False); \
    } \
}

int
main(int argc, char **argv)
{
    int             pagenum, prev_pagenum;
    int             i, w, h, t, x, y;
    int             wx, wy;
    char           *s;
    const char     *docfile;
    Pixmap          draw = 0;
    Link           *l = NULL, *ll = NULL;
    Imlib_Border    ibd;
    int            *page_hist = NULL;
    int             page_hist_len = 1;
    int             page_hist_pos = 0;

    w = EDOX_DEFAULT_W;
    h = EDOX_DEFAULT_H;
    pagenum = 0;

    docfile = "MAIN";
    for (i = 1; i < argc; i++)
    {
        if (!strcmp(argv[i], "-help"))
            usage(argv[0]);
        else if ((!strcmp(argv[i], "-page")) && (i < (argc - 1)))
            pagenum = atoi(argv[++i]);
        else if ((!strcmp(argv[i], "-file")) && (i < (argc - 1)))
            docfile = argv[++i];
        else if ((!strcmp(argv[i], "-size")) && (i < (argc - 2)))
        {
            w = atoi(argv[++i]);
            h = atoi(argv[++i]);
        }
        else
            docdir = strdup(argv[i]);
    }
    if (!docdir)
        docdir = strdup(doxdir);
    s = EMALLOC(char, strlen(docdir) + strlen(docfile) + 2 + 20);

    if (strstr(docdir, "/ABOUT"))
        show_top_bar = 0;

    /* now we'll set the locale */
    setlocale(LC_ALL, "");
    if (!XSupportsLocale())
        setlocale(LC_ALL, "C");
    XSetLocaleModifiers("");
    setlocale(LC_ALL, NULL);
    /* I dont want any internationalisation of my numeric input & output */
    setlocale(LC_NUMERIC, "C");

    sprintf(s, "%s/%s", docdir, docfile);

    LoadFile(s, docfile);

    if (!(disp = XOpenDisplay(NULL)))
    {
        printf("%s: Cannot open X display.\n", argv[0]);
        exit(0);
    }

    VRootInit();

    t = (show_top_bar) ? 16 : 0;

    wx = (VRoot.w - w) / 2;
    wy = (VRoot.h - (h + t)) / 2;
#ifdef USE_XINERAMA
    if (VRoot.win == DefaultRootWindow(disp) && XineramaIsActive(disp))
    {
        Window          rt, ch;
        int             d;
        unsigned int    ud;
        int             pointer_x, pointer_y;
        int             num;
        XineramaScreenInfo *screens;

        XQueryPointer(disp, VRoot.win, &rt, &ch, &pointer_x, &pointer_y,
                      &d, &d, &ud);

        screens = XineramaQueryScreens(disp, &num);
        for (i = 0; i < num; i++)
        {
            if (pointer_x >= screens[i].x_org &&
                pointer_x <= (screens[i].width + screens[i].x_org) &&
                pointer_y >= screens[i].y_org &&
                pointer_y <= (screens[i].height + screens[i].y_org))
            {
                wx = ((screens[i].width - w) / 2) + screens[i].x_org;
                wy = ((screens[i].height - (h + t)) / 2) + screens[i].y_org;
            }
        }

        XFree(screens);
    }
#endif
    win_main = CreateWindow(VRoot.win, wx, wy, w, h + t);
    XSelectInput(disp, win_main, KeyPressMask | KeyReleaseMask);
    win_text = XCreateSimpleWindow(disp, win_main, 0, t, w, h, 0, 0, 0);
    XSelectInput(disp, win_text, ButtonPressMask | ButtonReleaseMask |
                 PointerMotionMask);

    draw = XCreatePixmap(disp, win_text, w, h, VRoot.depth);

    l = RenderPage(draw, pagenum, w, h);
    UPDATE_NOW;

    if (show_top_bar)
    {
        win_title = XCreateSimpleWindow(disp, win_main,
                                        0, 0, (w - 64 - 64 - t), t, 0, 0, 0);
        win_prev = XCreateSimpleWindow(disp, win_main,
                                       (w - 64 - 64 - t), 0, 64, t, 0, 0, 0);
        win_next = XCreateSimpleWindow(disp, win_main,
                                       (w - 64 - 64 - t) + 64, 0, 64, t,
                                       0, 0, 0);
        win_exit = XCreateSimpleWindow(disp, win_main,
                                       (w - 64 - 64 - t) + 64 + 64, 0, t, t,
                                       0, 0, 0);
        XSelectInput(disp, win_prev, ButtonPressMask | ButtonReleaseMask);
        XSelectInput(disp, win_next, ButtonPressMask | ButtonReleaseMask);
        XSelectInput(disp, win_exit, ButtonPressMask | ButtonReleaseMask);

        im_title = ImageLoadDox("title.png");
        imlib_context_set_image(im_title);
        ibd.left = 50;
        ibd.right = 2;
        ibd.top = 2;
        ibd.bottom = 2;
        imlib_image_set_border(&ibd);

        im_prev1 = ImageLoadDox("prev1.png");
        im_prev2 = ImageLoadDox("prev2.png");
        im_next1 = ImageLoadDox("next1.png");
        im_next2 = ImageLoadDox("next2.png");
        im_exit1 = ImageLoadDox("exit1.png");
        im_exit2 = ImageLoadDox("exit2.png");

        ApplyImage1(win_title, im_title);
        ApplyImage1(win_prev, im_prev1);
        ApplyImage1(win_next, im_next1);
        ApplyImage1(win_exit, im_exit1);

        XMapWindow(disp, win_title);
        XMapWindow(disp, win_prev);
        XMapWindow(disp, win_next);
        XMapWindow(disp, win_exit);
    }
    XMapWindow(disp, win_text);
    XMapWindow(disp, win_main);

    XSync(disp, False);

    page_hist = EMALLOC(int, 1);

    page_hist[0] = 0;

    for (;;)
    {
        KeySym          keysym;
        XEvent          ev;

        prev_pagenum = pagenum;

        XNextEvent(disp, &ev);
        switch (ev.type)
        {
        case KeyPress:
            keysym = XLookupKeysym(&ev.xkey, 0);
            switch (keysym)
            {
            case XK_Escape:
                exit(0);
                break;
            case XK_Down:
                goto do_next;
            case XK_Up:
                goto do_prev;
            case XK_Home:
                pagenum = 0;
                page_hist_pos = 0;
                goto do_page;
            case XK_End:
                pagenum = 99999;
                goto do_page;
            case XK_Left:
            case XK_Prior:
                pagenum--;
                page_hist_len = page_hist_pos + 1;
                goto do_page;
            case XK_Right:
            case XK_Next:
                pagenum++;
                page_hist_len = page_hist_pos + 1;
                goto do_page;
            case XK_r:
                LoadFile(s, docfile);
                goto do_page1;
            }
            break;

          do_next:
            if (page_hist_pos >= page_hist_len - 1)
                break;
            page_hist_pos++;
            pagenum = page_hist[page_hist_pos];
            goto do_page;

          do_prev:
            if (pagenum == page_hist[page_hist_pos])
            {
                page_hist_pos--;
                if (page_hist_pos < 0)
                    page_hist_pos = 0;
            }
            pagenum = page_hist[page_hist_pos];
            goto do_page;

          do_page_save:
            pagenum = FixPage(pagenum);
            if (pagenum == prev_pagenum)
                break;
            page_hist_pos++;
            if (page_hist_pos >= page_hist_len)
            {
                page_hist_len++;
                page_hist = EREALLOC(int, page_hist, page_hist_len);
            }
            page_hist_len = page_hist_pos + 1;
            page_hist[page_hist_pos] = pagenum;
            goto do_page;

          do_page:
            pagenum = FixPage(pagenum);
            if (pagenum == prev_pagenum)
                break;
          do_page1:
            FREE_LINKS;
            l = RenderPage(draw, pagenum, w, h);
            UPDATE;
            break;

        case ButtonPress:
            if (ev.xbutton.window == win_prev)
                ApplyImage2(win_prev, im_prev2);
            else if (ev.xbutton.window == win_next)
                ApplyImage2(win_next, im_next2);
            else if (ev.xbutton.window == win_exit)
                ApplyImage2(win_exit, im_exit2);
            else
            {
                x = ev.xbutton.x;
                y = ev.xbutton.y;
                ll = l;
                while (ll)
                {
                    if ((x >= ll->x) && (y >= ll->y) &&
                        (x < (ll->x + ll->w)) && (y < (ll->y + ll->h)))
                    {
                        int             pg;

                        if (!strncmp("EXEC.", ll->name, 5))
                        {
                            if (!fork())
                            {
                                char           *exe;

                                exe = &(ll->name[5]);
                                execl("/bin/sh", "/bin/sh", "-c", exe, NULL);
                                exit(0);
                            }
                        }
                        else if (!strncmp("INPUT.", ll->name, 6))
                        {
                            FILE           *p;
                            char           *exe, tmp[1024];

                            exe = &(ll->name[6]);
                            if (exe[0] != '/')
                            {
                                sprintf(tmp, "%s/%s", docdir, exe);
                                exe = tmp;
                            }
                            p = popen(exe, "r");
                            if (p)
                            {
                                int             dirlen = 0;
                                char           *sp;

                                sp = exe;
                                while ((*sp) && (*sp != ' '))
                                    sp++;
                                while ((*sp != '/') && (sp != exe))
                                    sp--;
                                dirlen = sp - exe;
                                if (dirlen > 1)
                                {
                                    Efree(docdir);
                                    docdir = EMALLOC(char, dirlen + 1);

                                    memcpy(docdir, exe, dirlen);
                                    docdir[dirlen] = 0;
                                }
                                GetObjects(p);
                                pclose(p);
                                Efree(page_hist);
                                page_hist = EMALLOC(int, 1);

                                page_hist[0] = 0;
                                page_hist_len = 1;
                                pagenum = 0;
                                page_hist_pos = 0;
                                FREE_LINKS;
                                l = RenderPage(draw, pagenum, w, h);
                                UPDATE;
                            }
                        }
                        else
                        {
                            pg = GetPage(ll->name);
                            if (pg >= 0)
                            {
                                pagenum = pg;
                                goto do_page_save;
                            }
                        }
                        break;
                    }
                    ll = ll->next;
                }
            }
            break;

        case ButtonRelease:
            if (ev.xbutton.window == win_prev)
            {
                ApplyImage3(win_prev, im_prev1);
                goto do_prev;
            }
            else if (ev.xbutton.window == win_next)
            {
                ApplyImage3(win_next, im_next1);
                pagenum++;
                goto do_page_save;
            }
            else if (ev.xbutton.window == win_exit)
            {
                ApplyImage3(win_exit, im_exit1);
                exit(0);
            }
            break;

        case EnterNotify:
        case LeaveNotify:
            break;

        case MotionNotify:
            while (XCheckTypedEvent(disp, ev.type, &ev))
                ;
            {
                static Link    *pl = NULL;
                char            found = 0;

                x = ev.xmotion.x;
                y = ev.xmotion.y;
                ll = l;
                while (ll)
                {
                    if ((x >= ll->x) && (y >= ll->y) &&
                        (x < (ll->x + ll->w)) && (y < (ll->y + ll->h)))
                    {
                        GC              gc;
                        XGCValues       gcv;
                        int             r, g, b;
                        XColor          xclr;

                        if (pl != ll)
                        {
                            if (pl)
                            {
                                UPDATE_NOW;
                            }
                            GetLinkColors(pagenum, &r, &g, &b);
                            ESetColor(&xclr, r, g, b);
                            EAllocColor(&xclr);
                            gc = XCreateGC(disp, win_text, 0, &gcv);
                            XSetForeground(disp, gc, xclr.pixel);
                            XDrawRectangle(disp, win_text, gc, ll->x, ll->y,
                                           ll->w, ll->h);
                            XFreeGC(disp, gc);
                            pl = ll;
                        }
                        found = 1;
                        ll = NULL;
                    }
                    if (ll)
                        ll = ll->next;
                }
                if (!found)
                {
                    UPDATE_NOW;
                    pl = NULL;
                }
            }
            break;

        case ClientMessage:
            if (ev.xclient.message_type == ATOM_WM_PROTOCOLS &&
                (Atom) ev.xclient.data.l[0] == ATOM_WM_DELETE_WINDOW)
                goto done;
            break;

        default:
            break;
        }
    }
  done:
    free(page_hist);
    free(s);
    return 0;
}

void
ESetColor(XColor *pxc, int r, int g, int b)
{
    pxc->red = (r << 8) | r;
    pxc->green = (g << 8) | g;
    pxc->blue = (b << 8) | b;
}

void
EGetColor(XColor *pxc, int *pr, int *pg, int *pb)
{
    *pr = pxc->red >> 8;
    *pg = pxc->green >> 8;
    *pb = pxc->blue >> 8;
}
