// jmode (XIM Server)
// connection management

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

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include "xim.h"
#include "xdispatch.h"
#include <list>
#include <map>
#include <X11/Xatom.h>

#define BUF_SIZE 1024
#define TRANSPORT_UNIT 20
#define TRANSPORT_MAX 100

class XConnection :
    public Connection , public WindowIf
{
public:
    XConnection(Window clientWin, Window commWin);
    virtual ~XConnection();
    virtual void expose(Window){};
    virtual void destroy(Window);

    void readProc(XClientMessageEvent *);
    void writeProc();
    bool isValid(){return mIsValid;};
private:
    bool readToBuf(XClientMessageEvent *);
    bool checkByteorder();
    void shiftBuffer(int );
    void doSend(TxPacket *t);

    Window mClientWin,mCommWin;
    bool mIsValid;
    struct {
	char *buf;
	int len;
    }mBuf;
};

static std::map<Window,XConnection *> gXConnections;
static Atom xim_xconnect;
static Atom xim_protocol;
static Atom xim_moredata;
static Atom jmode_comm;

static void procNewXConnection(XClientMessageEvent *ev)
{
    XClientMessageEvent r;
    Window comWin,clientWin;

    comWin = XCreateSimpleWindow(gDpy, ev->window,
				 0, 0, 1, 1, 0, 0, 0);
    clientWin = ev->data.l[0];

    r.type = ClientMessage;
    r.window = clientWin;
    r.message_type = xim_xconnect;
    r.format = 32;
    r.data.l[0] = comWin;
    r.data.l[1] = 0;
    r.data.l[2] = 2;
    r.data.l[3] = TRANSPORT_MAX;
    XSendEvent(gDpy, clientWin, False, NoEventMask, (XEvent *)&r);

    XConnection *xc = new XConnection(clientWin, comWin);
    std::pair<Window,XConnection *> p(comWin, xc);
    gXConnections.insert(p);
}

static void reapOldConnection()
{
    std::map<Window,XConnection *>::iterator it;
    for (it = gXConnections.begin(); it != gXConnections.end(); it ++) {
	XConnection *xc = (*it).second;
	if (!xc->isValid()) {
	    delete xc;
	    gXConnections.erase(it);
	    return ;
	}
    }
}

void procXClientMessage(XClientMessageEvent *ev)
{
    if (ev->window == gWnd) {
	procNewXConnection(ev);
	reapOldConnection();
    }else{
	std::map<Window,XConnection *>::iterator i;
	i = gXConnections.find(ev->window);
	if (i != gXConnections.end()) {
	    XConnection *xc = (*i).second;
	    int error_count = dpy_error_count;
	    xc->readProc(ev);
	    if (error_count != dpy_error_count) {
		printf("failed to read\n");
		xc->terminate();
	    }
	}else {
	    printf("packet for unknown window\n");
	}
    }
}

XConnection::XConnection(Window clientWin, Window commWin)
{
    mClientWin = clientWin;
    mCommWin = commWin;
    mBuf.buf = (char *)malloc(BUF_SIZE);
    mBuf.len = 0;
    add_window_watch(mClientWin, this, STRUCTURE_NOTIFY_MASK);
    mIsValid = true;
}

XConnection::~XConnection()
{
    XDestroyWindow(gDpy, mCommWin);
    free(mBuf.buf);
}

void XConnection::destroy(Window w)
{
    if (mIsValid) {
	OnClose();
    }
    mIsValid = false;
}

void XConnection::readProc(XClientMessageEvent *ev)
{
    if (!readToBuf(ev)) {
	return ;
    }
    checkByteorder();

    bool pushed = true;
    do{
	int len = -1;
	if (mBuf.len >= 4) {
	    len = RxPacket::getPacketLength((unsigned char *)mBuf.buf,
					    mByteorder);
	}
	if ((len > 4 && len <= mBuf.len) ||
	    (len == 4 && mBuf.buf[0] == XIM_DISCONNECT)) {
	    RxPacket *p = 
		createRxPacket((unsigned char *)mBuf.buf, mByteorder);
	    shiftBuffer(len);
	    mRxQ.push_back(p);
	} else if (len == 4) {
	    // do nothing
	    shiftBuffer(4);
	} else {
	    pushed = false;
	}
    } while(pushed);
    OnRecv();

    writeProc();
}

void XConnection::shiftBuffer(int len)
{
    memmove(mBuf.buf, &mBuf.buf[len], mBuf.len - len);
    mBuf.len -= len;
}

bool XConnection::checkByteorder()
{
    if (mByteorder == BYTEORDER_UNKNOWN) {
	if (mBuf.buf[0] != XIM_CONNECT) {
	    printf("not XIM_CONNECT\n");
	    return false;
	}
	if (mBuf.buf[4] == 0x42) {
	    mByteorder = MSB_FIRST;
	}else if (mBuf.buf[4] == 0x6c) {
	    mByteorder = LSB_FIRST;
	}else{
	    return false;
	}
    }
    return true;
}

bool XConnection::readToBuf(XClientMessageEvent *ev)
{
    if (ev->format == 32 && ev->message_type == xim_protocol) {
	// indirect
	int offset = 0;
	Atom type;
	int format;
	unsigned long nrItems;
	unsigned long remain;
	char *data;
	do{
	    XGetWindowProperty(gDpy, ev->window, ev->data.l[1],
			       offset, BUF_SIZE - mBuf.len,True,
			       AnyPropertyType,
			       &type, &format, &nrItems, &remain,
			       (unsigned char **)&data);
	    if (!data) {
		return false;
	    }
	    if (format == 8) {
		memcpy(&mBuf.buf[mBuf.len], data, nrItems);
		mBuf.len += nrItems;
	    } else {
		return false;
	    }
	    XFree(data);
	} while (remain > 0);
    } else if (ev->format == 8) {
	// direct
	if (mBuf.len + TRANSPORT_UNIT >= BUF_SIZE) {
	    return false;
	}
	memcpy(&mBuf.buf[mBuf.len], ev->data.b, TRANSPORT_UNIT);
	mBuf.len += TRANSPORT_UNIT;// XXX may over run
    } else {
	return false;
    }
    return true;
}

void XConnection::writeProc()
{
    std::list<TxPacket *>::iterator i;
    if (!mTxQ.size()) {
	return ;
    }

    OnSend();


    int error_count = dpy_error_count;
    while(mPTxQ.size()) {
	i = mPTxQ.begin();
	doSend(*i);
	delete *i;
	mPTxQ.pop_front();
    }

    while (mTxQ.size()) {
	i = mTxQ.begin();
	doSend(*i);
	delete *i;
	mTxQ.pop_front();
    }

    XFlush(gDpy);
    if (error_count != dpy_error_count) {
	printf("failed to write\n");
	terminate();
    }
    if (mIsCloseWait) {
	mClientWin = None;
	mIsValid = false;
	OnClose();
    }
}

void XConnection::doSend(TxPacket *t)
{
    XClientMessageEvent r;
    int buflen;
    char *buf;

    buflen = t->get_length();
    buf = (char *)alloca(buflen);
    t->write_to_buf((unsigned char*)buf, buflen, mByteorder);
    if (buflen < TRANSPORT_MAX) {
	// via event
	int offset = 0;
	int length = buflen;
	while (length > TRANSPORT_UNIT) {
	    r.type = ClientMessage;
	    r.window = mClientWin;
	    r.format = 8;
	    r.message_type = xim_moredata;
	    memcpy(r.data.b, &buf[offset], TRANSPORT_UNIT);
	    XSendEvent(gDpy, mClientWin, False, NoEventMask, (XEvent *)&r);
	    offset += TRANSPORT_UNIT;
	    length -= TRANSPORT_UNIT;
	}
	r.type = ClientMessage;
	r.window = mClientWin;
	r.format = 8;
	r.message_type = xim_protocol;
	memset(r.data.b, 0, TRANSPORT_UNIT);
	memcpy(r.data.b, &buf[offset], length);
	XSendEvent(gDpy, mClientWin, False, NoEventMask, (XEvent *)&r);
    } else {
	// via property
	r.type = ClientMessage;
	r.window = mClientWin;
	r.format = 32;
	r.message_type = xim_protocol;
	r.data.l[0] = buflen;
	r.data.l[1] = jmode_comm;
	XChangeProperty(gDpy, mClientWin, jmode_comm, XA_STRING,
			8, PropModeAppend, (unsigned char *)buf, buflen);
	XSendEvent(gDpy, mClientWin, False, NoEventMask, (XEvent *)&r);
    }
}

int connection_setup()
{
    xim_xconnect = XInternAtom(gDpy, "_XIM_XCONNECT", False);
    xim_protocol = XInternAtom(gDpy, "_XIM_PROTOCOL", False);
    xim_moredata = XInternAtom(gDpy, "_XIM_MOREDATA", False);
    jmode_comm = XInternAtom(gDpy, "JMODE_COMM", False);
    return 0;
}
/*
 * Local variables:
 *  c-indent-level: 4
 *  c-basic-offset: 4
 * End:
 */
