// 1,selectのループを抽象化している、今のところはgdkの機能を使って、
// GUI のループと統合をしている。
// 2,event dispatch for GtkWidget and Xlib Window.
// 3,XIMの通信の確立を行う。

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <gtk/gtk.h>
#ifdef USE_GNOME_APPLET
#include <applet-widget.h>
#endif
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <unistd.h>
#include <stdio.h>
#include <map>

#include "jmode.h"
#include "gtkdispatch.h"
#include "xdispatch.h"
#include "xim.h"

static Atom xim_servers;
static Atom server_heke;
static Atom locales;
static Atom transport;

// selectionを受けるためのウィンドウ
Window gWnd;
//汎用のディスプレイ接続
Display *gDpy;
int dpy_error_count;

struct widget_watch_struct {
    GtkWidget *w;
    WidgetIf *i;
    int mask;
};

static Dispatcher *dispatcher;

static int (*default_error_handler)(Display *d, XErrorEvent *e);
static int
X_ErrorHandler(Display *d, XErrorEvent *e)
{
    if (d != gDpy) {
	return default_error_handler(d, e);
    }
    dpy_error_count ++;
    printf("X error occured.\n");
    return 0;
}

static int (*default_io_error_handler)(Display *d);
static int
X_IOErrorHandler(Display *d)
{
    if (d != gDpy) {
	return default_io_error_handler(d);
    }
    printf("X IO error occured.\n");
    global_finalize(0);
    return 0;
}

class GtkFDDispatcher : public Dispatcher {
public:
    virtual ~GtkFDDispatcher(){};
    virtual void addFDWatch(int fd, int mask, void (*f)(int, int));
    virtual void removeFDWatch(int fd);
    virtual void mainLoop();

    void fdEventHandler(int fd, GdkInputCondition c);

private:
    struct fd_watch_struct {
	int read_tag,write_tag;
	int mask;
	void (*fn)(int,int);
    };
    static void fdEventHandlerGate(gpointer p, gint fd, GdkInputCondition c);
    void removeCurrentWatch(int fd);
    std::map<int,fd_watch_struct> fd_watch_stat;
};

static std::map<unsigned int,WindowIf *> window_watch_stat;
static std::map<GtkWidget *,widget_watch_struct *>widget_watch_stat;

// fd watchの部分
void
GtkFDDispatcher::fdEventHandler(int fd, GdkInputCondition c)
{
    // GdkInputConditionはenumなのに or されてくる。
    std::map<int,fd_watch_struct>::iterator i;
    int ev;
    i = fd_watch_stat.find(fd);
    if (i == fd_watch_stat.end()) {
        // danger ..
        printf("Unknown fd %d event.\n", fd);
        return ;
    }
    ev = 0;
    if (c & GDK_INPUT_READ) {
        (*i).second.fn(fd, READ_OK);
    }
    if (c & GDK_INPUT_WRITE) {
        (*i).second.fn(fd, WRITE_OK);
    }
}

void
GtkFDDispatcher::fdEventHandlerGate(gpointer p, gint fd,
				    GdkInputCondition c)
{
    GtkFDDispatcher *dpt = (GtkFDDispatcher *)p;
    dpt->fdEventHandler(fd, c);
}

void
GtkFDDispatcher::removeCurrentWatch(int fd)
{
    std::map<int,fd_watch_struct>::iterator i;
    i = fd_watch_stat.find(fd);
    if (i == fd_watch_stat.end()) {
        return ;
    }
    if ((*i).second.mask & READ_OK) {
        gdk_input_remove((*i).second.read_tag);
    }
    if ((*i).second.mask & WRITE_OK) {
        gdk_input_remove((*i).second.write_tag);
    }
}

Dispatcher *get_dispatcher()
{
    if (!dispatcher) {
	dispatcher = new GtkFDDispatcher();
    }
    return dispatcher;
}

void
GtkFDDispatcher::addFDWatch(int fd, int mask, void (*fn)(int , int))
{
    removeCurrentWatch(fd);

    int rtag=0, wtag=0;
    if (mask & READ_OK) {
        rtag = gdk_input_add(fd, (GdkInputCondition)GDK_INPUT_READ,
                             fdEventHandlerGate, this);
    }

    if (mask & WRITE_OK) {
        wtag = gdk_input_add(fd, (GdkInputCondition)GDK_INPUT_WRITE,
                             fdEventHandlerGate, this);
    }

    fd_watch_struct s;
    s.mask = mask;
    s.fn = fn;
    s.read_tag = rtag;
    s.write_tag = wtag;
    std::pair<int,fd_watch_struct> p(fd, s);
    fd_watch_stat.insert(p);
}

void
GtkFDDispatcher::mainLoop()
{
#ifdef USE_GNOME_APPLET
    if (!(g_option_mask & OPT_TOOLBAR)){
	applet_widget_gtk_main();
    } else
#endif
	gtk_main();
}

void
GtkFDDispatcher::removeFDWatch(int fd)
{
    std::map<int,fd_watch_struct>::iterator i;
    i = fd_watch_stat.find(fd);
    if (i != fd_watch_stat.end()) {
        if ((*i).second.mask & READ_OK) {
            gdk_input_remove((*i).second.read_tag);
        }
        if ((*i).second.mask & WRITE_OK) {
            gdk_input_remove((*i).second.write_tag);
        }
        fd_watch_stat.erase(i);
    }
}

void
add_window_watch(Window id, WindowIf *w, int mask)
{
    std::pair<unsigned int,WindowIf *> p(id, w);
    window_watch_stat.insert(p);

    //イベントマスクはX.hに定義されているものと同じ値を持つが、
    //それに依存したプログラムは書かない。
    int emask = 0;
    if (mask & EXPOSE_MASK) {
	emask |= ExposureMask;
    }
    if (mask & STRUCTURE_NOTIFY_MASK) {
	emask |= StructureNotifyMask;
    }
    XSelectInput(gDpy, id, emask);
}

void
remove_window_watch(Window id)
{
    std::map<unsigned int,WindowIf *>::iterator i;
    i = window_watch_stat.find(id);
    if (i != window_watch_stat.end()) {
	window_watch_stat.erase(i);
    }
}

// GtkWidgetのイベントハンドラ
//
static widget_watch_struct *
find_widget_watch(GtkWidget *g)
{
    std::map<GtkWidget *,widget_watch_struct *>::iterator i;
    i = widget_watch_stat.find(g);
    if (i != widget_watch_stat.end()) {
	return (*i).second;
    }
    return NULL;
}

static void
widget_destroy(GtkWidget *w, gpointer p)
{
    std::map<GtkWidget *,widget_watch_struct *>::iterator i;
    i = widget_watch_stat.find(w);
    if (i != widget_watch_stat.end()) {
	widget_watch_struct *s;
	s = (*i).second;
	if (s->mask & WIDGET_DESTROY) {
	    s->i->destroy(w);
	}
	free(s);
	widget_watch_stat.erase(i);
    }
}

static gint
expose_event(GtkWidget *g, GdkEventExpose *e)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->expose(g);
    }
    return FALSE;
}

static void
clicked_event(GtkWidget *g, gpointer p)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->clicked(g);
    }
}

static gint
activate_event(GtkWidget *g, gpointer p)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->activate(g, p);
    }
    return TRUE;
}

static void
select_row_event(GtkWidget *g, gint row, gint column,
		 GdkEventButton *, gpointer *)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->select_row(g, row, column);
    }
}

static void
key_press_event(GtkWidget *g, GdkEventKey *e,
		gpointer u)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->key_press(g, e);
    }
}

static void
resize_event(GtkWidget *g, GdkEventConfigure *event)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->resize(g, g->allocation.width, g->allocation.height);
    }
}

static void
button_press_event(GtkWidget *g, GdkEventButton *event)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->button_press(g, (int)event->x, (int)event->y, event->button);
    }
}

static void
button_release_event(GtkWidget *g, GdkEventButton *event)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->button_release(g, (int)event->x, (int)event->y, event->button);
    }
}

static void
motion_notify_event(GtkWidget *g, GdkEventMotion *event)
{
    widget_watch_struct *w;
    w = find_widget_watch(g);
    if (w) {
        w->i->motion(g, (int)event->x, (int)event->y, event->state);
    }
}

static gint
delete_event(GtkWidget *g, GdkEvent *e, gpointer p)
{
    return FALSE;
}

static char *
signal_name(int m)
{
    switch (m) {
    case WIDGET_DESTROY:return "destroy";
    case WIDGET_EXPOSE:return "expose_event";
    case WIDGET_BUTTON_PRESS:return "button_press_event";
    case WIDGET_CLICK:return "clicked";
    case WIDGET_ACTIVATE:return "activate";
    case WIDGET_DELETE:return "delete_event";
    case WIDGET_ROW_SELECTED:return "select_row";
    case WIDGET_KEY_PRESS:return "key_press_event";
    case WIDGET_RESIZE:return "configure_event";
    case WIDGET_MOTION:return "motion_notify_event";
    case WIDGET_BUTTON_RELEASE:return "button_release_event";
    }
    printf("unknown signal\n");
    return 0;
}

void
add_widget_watch(GtkWidget *g, int mask,
		 WidgetIf *w, void *opt)
{
    widget_watch_struct *s =find_widget_watch(g);
    if (!s) {
	//初登録
	s = (widget_watch_struct *)
	    malloc(sizeof(widget_watch_struct));
	s->w = g;
	s->i = w;
	s->mask = 0;
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_DESTROY),
			   GTK_SIGNAL_FUNC(widget_destroy), opt);
    }
  
    int emask = 0;
    s->mask |= mask;
    if (mask & WIDGET_EXPOSE) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_EXPOSE),
			   GTK_SIGNAL_FUNC(expose_event), opt);
	emask |= GDK_EXPOSURE_MASK;
    }
    if (mask & WIDGET_CLICK) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_CLICK),
			   GTK_SIGNAL_FUNC(clicked_event), opt);
    }
    if (mask & WIDGET_ACTIVATE) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_ACTIVATE),
			   GTK_SIGNAL_FUNC(activate_event), opt);
    }
    if (mask & WIDGET_DELETE) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_DELETE),
			   GTK_SIGNAL_FUNC(delete_event), opt);
    }
    if (mask & WIDGET_ROW_SELECTED) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_ROW_SELECTED),
			   GTK_SIGNAL_FUNC(select_row_event), opt);
    }
    if (mask & WIDGET_KEY_PRESS) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_KEY_PRESS),
			   GTK_SIGNAL_FUNC(key_press_event), opt);
    }
    if (mask & WIDGET_RESIZE) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_RESIZE),
			   GTK_SIGNAL_FUNC(resize_event), opt);
    }
    if (mask & WIDGET_BUTTON_PRESS) {
	gtk_signal_connect(GTK_OBJECT(g),signal_name(WIDGET_BUTTON_PRESS),
			   GTK_SIGNAL_FUNC(button_press_event),opt);
    }
    if (mask & WIDGET_BUTTON_RELEASE) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_BUTTON_RELEASE),
			   GTK_SIGNAL_FUNC(button_release_event), opt);
    }
    if (mask & WIDGET_MOTION) {
	gtk_signal_connect(GTK_OBJECT(g), signal_name(WIDGET_MOTION),
			   GTK_SIGNAL_FUNC(motion_notify_event), opt);
    }
    if (emask) {
	gtk_widget_set_events(g,gtk_widget_get_events(g) | emask);
    }
    std::pair<GtkWidget *,widget_watch_struct *> p(g, s);
    widget_watch_stat.insert(p);
}

void
remove_widget_watch(GtkWidget *w)
{
    std::map<GtkWidget *,widget_watch_struct *>::iterator i;
  
    i = widget_watch_stat.find(w);
    if (i != widget_watch_stat.end()) {
	// XXX signal_disconnectを呼んでない。
	free((*i).second);
	widget_watch_stat.erase(i);
    }else{
	//見付からなかったら、先にdestroyされたとして、
	//それでOK
    }
}

WidgetIf::~WidgetIf()
{
}

//
static void
registerAtoms()
{
    xim_servers = XInternAtom(gDpy, "XIM_SERVERS", 0);
    server_heke = XInternAtom(gDpy, "@server=jmode", 0);
    locales = XInternAtom(gDpy, "LOCALES", 0);
    transport = XInternAtom(gDpy, "TRANSPORT", 0);
}

static bool
registerProp()
{
    Atom type;
    int format;
    unsigned long nr_prop,nr_bytes;
    Atom *prop;
    int mode = PropModePrepend;
    int valuechange = 1;
    XGetWindowProperty(gDpy, DefaultRootWindow(gDpy),
                       xim_servers, 0, 8192 ,False,
                       XA_ATOM, &type, &format,
                       &nr_prop, &nr_bytes, (unsigned char **)&prop);
    int i;
    if (type != XA_ATOM || format != 32) {
	mode = PropModeReplace;
    } else {
	for (i = 0 ; i < (int)nr_prop ; i++) {
	    if (prop[i] == server_heke) {
		mode = PropModeAppend;
		valuechange = 0;
		break;
	    }
	}
    }
    if (nr_prop) {
	XFree(prop);
    }

    XChangeProperty(gDpy, DefaultRootWindow(gDpy), xim_servers,
		    XA_ATOM, 32,
		    mode, (unsigned char *)&server_heke,
		    valuechange ? 1 : 0);
    return true;
}

bool
pretrans_register()
{
    gDpy = XOpenDisplay(NULL);
    if (!gDpy) {
        printf("failed to open display!\n");
        return false;
    }
    registerAtoms();
    if (!registerProp()) {
	XCloseDisplay(gDpy);
	return false;
    }
    XFlush(gDpy);
    scr_width = DisplayWidth(gDpy, 0);
    scr_height = DisplayHeight(gDpy, 0);
    return true;
}

static void
sendSelectionNotify(XEvent *ev, char *buf, int len)
{
    XEvent e;
    e.type = SelectionNotify;
    e.xselection.requestor = ev->xselectionrequest.requestor;
    e.xselection.selection = ev->xselectionrequest.selection;
    e.xselection.target = ev->xselectionrequest.target;
    e.xselection.time = ev->xselectionrequest.time;
    e.xselection.property = ev->xselectionrequest.property;
    XChangeProperty(gDpy, e.xselection.requestor,
                    e.xselection.property,
                    e.xselection.target,
                    8,PropModeReplace,
                    (unsigned char *)buf, len);
    XSendEvent(gDpy, e.xselection.requestor, 0, 0, &e);
    XFlush(gDpy);
}

void
notifyLocale(XEvent *ev)
{
    char buf[32];
    strcpy(buf,"@locale=ja_JP");
    sendSelectionNotify(ev, buf, strlen(buf)+1);
    if (g_option_mask & OPT_TRACE) {
	printf("selection notify request for locale.\n");
    }
}

void
notifyTransport(XEvent *ev)
{
    sendSelectionNotify(ev, "@transport=X/", 13+1);
    if (g_option_mask & OPT_TRACE) {
	printf("selection notify request for transport.\n");
    }
}

WindowIf *
findWindowIf(Window w)
{
    std::map<unsigned int,WindowIf *>::iterator i;
    i = window_watch_stat.find(w);
    if (i == window_watch_stat.end()) {
	return NULL;
    }
    return (*i).second;
}

WindowIf::~WindowIf()
{
}

void
WindowIf::resize(Window, int, int)
{
    // do nothing
}

void
ProcXEvent(XEvent *e)
{
    Atom p;
    switch (e->type) {
    case SelectionRequest:
    {
	p = e->xselectionrequest.property;
	if (p == locales) {
	    notifyLocale(e);
	} else if(p == transport) {
	    notifyTransport(e);
	} else {
	    printf("property %s?\n",
		   XGetAtomName(gDpy,e->xselection.property));
	    break;
	}
    }
    break;
    case Expose:
    {
	if (e->xexpose.count == 0) {
	    WindowIf *i = findWindowIf(e->xexpose.window);
	    if (i) {
		i->expose(e->xexpose.window);
	    }
	}
    }
    break;
    case ConfigureNotify:
    {
	WindowIf *i = findWindowIf(e->xconfigure.window);
	if (i) {
	    i->resize(e->xconfigure.window,
		      e->xconfigure.x, e->xconfigure.y);
	}
    }
    break;
    case DestroyNotify:
    {
	WindowIf *i = findWindowIf(e->xdestroywindow.window);
	if (i) {
	    i->destroy(e->xdestroywindow.window);
	}
	remove_window_watch(e->xdestroywindow.window);
    }
    break;
    case ClientMessage:
	procXClientMessage(&e->xclient);
    break;
    default:;
	//printf("unknown type of X event. %d\n",e->type);
    }
}

static void
xEventRead(int fd, int ev)
{
    XFlush(gDpy);

    XEvent e;
    while (XPending(gDpy)) {
	XNextEvent(gDpy, &e);
    
	// gtkの設定したhandlerを上書きする
	default_error_handler = XSetErrorHandler(X_ErrorHandler);
	default_io_error_handler = XSetIOErrorHandler(X_IOErrorHandler);
	//
	ProcXEvent(&e);
	// 戻す
	XSetErrorHandler(default_error_handler);
	XSetIOErrorHandler(default_io_error_handler);
    }
}

int
pretrans_setup()
{
    int fd;
  
    gWnd = XCreateSimpleWindow(gDpy, DefaultRootWindow(gDpy),
			       0, 0, 1, 1,
			       1,0,0);

    XSetSelectionOwner(gDpy, server_heke, gWnd, CurrentTime);
    XSelectInput(gDpy, DefaultRootWindow(gDpy), 0);

    XSync(gDpy, False);

    fd = XConnectionNumber(gDpy);
    Dispatcher *dpt = get_dispatcher();
    dpt->addFDWatch(fd, READ_OK, xEventRead);

    return fd;
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
