/*------------------------------------------------------------------------
 *  Copyright 2007-2009 (c) Jeff Brown <spadix@users.sourceforge.net>
 *
 *  This file is part of the ZBar Bar Code Reader.
 *
 *  The ZBar Bar Code Reader is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Lesser Public License as
 *  published by the Free Software Foundation; either version 2.1 of
 *  the License, or (at your option) any later version.
 *
 *  The ZBar Bar Code Reader is distributed in the hope that it will be
 *  useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 *  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser Public License
 *  along with the ZBar Bar Code Reader; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 *  Boston, MA  02110-1301  USA
 *
 *  http://sourceforge.net/projects/zbar
 *------------------------------------------------------------------------*/

#include "posix.h"
#include "processor.h"
#include "window.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

static inline int x_handle_event(zbar_processor_t *proc)
{
    XEvent ev;
    XNextEvent(proc->display, &ev);

    switch (ev.type) {
    case Expose: {
	/* FIXME ignore when running(?) */
	XExposeEvent *exp = (XExposeEvent *)&ev;
	while (1) {
	    assert(ev.type == Expose);
	    _zbar_window_expose(proc->window, exp->x, exp->y, exp->width,
				exp->height);
	    if (!exp->count)
		break;
	    XNextEvent(proc->display, &ev);
	}
	zbar_window_redraw(proc->window);
	break;
    }

    case ConfigureNotify:
	zprintf(3, "resized to %d x %d\n", ev.xconfigure.width,
		ev.xconfigure.height);
	zbar_window_resize(proc->window, ev.xconfigure.width,
			   ev.xconfigure.height);
	_zbar_processor_invalidate(proc);
	break;

    case ClientMessage:
	if ((ev.xclient.message_type ==
	     XInternAtom(proc->display, "WM_PROTOCOLS", 0)) &&
	    ev.xclient.format == 32 &&
	    (ev.xclient.data.l[0] ==
	     XInternAtom(proc->display, "WM_DELETE_WINDOW", 0))) {
	    zprintf(3, "WM_DELETE_WINDOW\n");
	    return (_zbar_processor_handle_input(proc, -1));
	}
	break;

    case KeyPress: {
	KeySym key = XLookupKeysym(&ev.xkey, 0);
	if (IsModifierKey(key))
	    break;
	if ((key & 0xff00) == 0xff00)
	    key &= 0x00ff;
	zprintf(16, "KeyPress(%04lx)\n", key);
	/* FIXME this doesn't really work... */
	return (_zbar_processor_handle_input(proc, key & 0xffff));
    }
    case ButtonPress: {
	zprintf(16, "ButtonPress(%d)\n", ev.xbutton.button);
	int idx = 1;
	switch (ev.xbutton.button) {
	case Button2:
	    idx = 2;
	    break;
	case Button3:
	    idx = 3;
	    break;
	case Button4:
	    idx = 4;
	    break;
	case Button5:
	    idx = 5;
	    break;
	}
	return (_zbar_processor_handle_input(proc, idx));
    }

    case DestroyNotify:
	zprintf(16, "DestroyNotify\n");
	zbar_window_attach(proc->window, NULL, 0);
	proc->xwin = 0;
	return (0);

    default:
	/* ignored */;
    }
    return (0);
}

static int x_handle_events(zbar_processor_t *proc)
{
    int rc = 0;
    while (rc >= 0 && XPending(proc->display))
	rc = x_handle_event(proc);
    return (rc);
}

static int x_connection_handler(zbar_processor_t *proc, int i)
{
    _zbar_mutex_lock(&proc->mutex);
    _zbar_processor_lock(proc);
    _zbar_mutex_unlock(&proc->mutex);

    x_handle_events(proc);

    _zbar_mutex_lock(&proc->mutex);
    _zbar_processor_unlock(proc, 0);
    _zbar_mutex_unlock(&proc->mutex);
    return (0);
}

static int x_internal_handler(zbar_processor_t *proc, int i)
{
    XProcessInternalConnection(proc->display, proc->state->polling.fds[i].fd);
    x_connection_handler(proc, i);
    return (0);
}

static void x_internal_watcher(Display *display, XPointer client_arg, int fd,
			       Bool opening, XPointer *watch_arg)
{
    zbar_processor_t *proc = (void *)client_arg;
    if (opening)
	add_poll(proc, fd, x_internal_handler);
    else
	remove_poll(proc, fd);
}

int _zbar_processor_open(zbar_processor_t *proc, char *title, unsigned width,
			 unsigned height)
{
    proc->display = XOpenDisplay(NULL);
    if (!proc->display)
	return (err_capture_str(proc, SEV_ERROR, ZBAR_ERR_XDISPLAY, __func__,
				"unable to open X display",
				XDisplayName(NULL)));

    add_poll(proc, ConnectionNumber(proc->display), x_connection_handler);
    XAddConnectionWatch(proc->display, x_internal_watcher, (void *)proc);
    /* must also flush X event queue before polling */
    proc->state->pre_poll_handler = x_connection_handler;

    int screen = DefaultScreen(proc->display);
    XSetWindowAttributes attr;
    attr.event_mask =
	(ExposureMask | StructureNotifyMask | KeyPressMask | ButtonPressMask);

    proc->xwin = XCreateWindow(proc->display, RootWindow(proc->display, screen),
			       0, 0, width, height, 0, CopyFromParent,
			       InputOutput, CopyFromParent, CWEventMask, &attr);
    if (!proc->xwin) {
	XCloseDisplay(proc->display);
	return (err_capture(proc, SEV_ERROR, ZBAR_ERR_XPROTO, __func__,
			    "creating window"));
    }

    XStoreName(proc->display, proc->xwin, title);

    XClassHint *class_hint = XAllocClassHint();
    class_hint->res_name   = "zbar";
    class_hint->res_class  = "zbar";
    XSetClassHint(proc->display, proc->xwin, class_hint);
    XFree(class_hint);
    class_hint = NULL;

    Atom wm_delete_window = XInternAtom(proc->display, "WM_DELETE_WINDOW", 0);
    if (wm_delete_window)
	XSetWMProtocols(proc->display, proc->xwin, &wm_delete_window, 1);

    if (zbar_window_attach(proc->window, proc->display, proc->xwin))
	return (err_copy(proc, proc->window));
    return (0);
}

int _zbar_processor_close(zbar_processor_t *proc)
{
    if (proc->window)
	zbar_window_attach(proc->window, NULL, 0);

    if (proc->display) {
	if (proc->xwin) {
	    XDestroyWindow(proc->display, proc->xwin);
	    proc->xwin = 0;
	}
	proc->state->pre_poll_handler = NULL;
	remove_poll(proc, ConnectionNumber(proc->display));
	XCloseDisplay(proc->display);
	proc->display = NULL;
    }
    return (0);
}

int _zbar_processor_invalidate(zbar_processor_t *proc)
{
    if (!proc->display || !proc->xwin)
	return (0);
    XClearArea(proc->display, proc->xwin, 0, 0, 0, 0, 1);
    XFlush(proc->display);
    return (0);
}

int _zbar_processor_set_size(zbar_processor_t *proc, unsigned width,
			     unsigned height)
{
    if (!proc->display || !proc->xwin)
	return (0);

    /* refuse to resize greater than (default) screen size */
    XWindowAttributes attr;
    XGetWindowAttributes(proc->display, proc->xwin, &attr);

    int maxw = WidthOfScreen(attr.screen);
    int maxh = HeightOfScreen(attr.screen);
    int w, h;
    if (width > maxw) {
	h = (maxw * height + width - 1) / width;
	w = maxw;
    } else {
	w = width;
	h = height;
    }
    if (h > maxh) {
	w = (maxh * width + height - 1) / height;
	h = maxh;
    }
    assert(w <= maxw);
    assert(h <= maxh);

    XResizeWindow(proc->display, proc->xwin, w, h);
    XFlush(proc->display);
    return (0);
}

int _zbar_processor_set_visible(zbar_processor_t *proc, int visible)
{
    if (visible)
	XMapRaised(proc->display, proc->xwin);
    else
	XUnmapWindow(proc->display, proc->xwin);
    XFlush(proc->display);
    return (0);
}
