#include <stdio.h>
#include <limits.h>
#include <iconv.h>

#include <X11/Xmd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include "xaux_common.h"
#include "xaux_ext_common.h"
#include "le_aux_protocol.h"


static Bool xaux_ext_get_sowin(xaux_class_t *xc, Display *display);
static Bool xaux_ext_process_property_update(Display *display, Window window, Atom message_type, Atom atom);

static aux_ext_data_t* convertAuxExtData(aux_ext_data_t* src, int Utf82Utf16)
{
    static iconv_t ic16_8 = (iconv_t)-1;
    static iconv_t ic8_16 = (iconv_t)-1;

    int i, sz;
    char* sp, *dp;
    size_t srclen, dstlen;

    aux_ext_data_t* dst = (aux_ext_data_t*) malloc (sizeof(aux_ext_data_t));

    if (ic16_8 == (iconv_t)-1) ic16_8 = iconv_open("UTF-8", "UTF-16");
    if (ic8_16 == (iconv_t)-1) ic8_16 = iconv_open("UTF-16", "UTF-8");


    *dst = *src;
    dst->string_ptr = NULL;
    dst->string_list = NULL;
    if (dst->string_count) {
        dst->string_list = (aux_ext_string_t*)calloc(dst->string_count, sizeof(aux_ext_string_t));

        for (i=0; i < dst->string_count; ++i) {
            srclen = sz = src->string_list[i].length;
            sz = (sz+1)*((Utf82Utf16)?2:3);
            dst->string_list[i].ptr = (char*)malloc(sz);
            memset(dst->string_list[i].ptr, 0, sz);

            sp = src->string_list[i].ptr;
            dp = dst->string_list[i].ptr;
            dstlen = sz;
            if (Utf82Utf16) {
                iconv(ic8_16, &sp, &srclen, &dp, &dstlen);
            } else {
                iconv(ic16_8, &sp, &srclen, &dp, &dstlen);
            }
            dst->string_list[i].length = sz - dstlen;
        }
    }

    return dst;
}

static freeConvertedAuxExtData(aux_ext_data_t* dst)
{
    int i;
    if (dst) {
        if (dst->string_list) {
            for (i=0; i < dst->string_count; ++i) {
                if (dst->string_list[i].ptr)
                    free(dst->string_list[i].ptr);
            }
            free(dst->string_list);
        }
        free(dst);
    }
}

/************************************************************
    functions to process xaux_class_t and aux messages
************************************************************/
Bool
xaux_ext_init_classes(
        Display		*display,
        xaux_class_t	*p,
        Window		extwin)
{
        char		buf[1024];
        int		i;

        DEBUG_printf("xaux_ext_init_classes classname:%s, extwin:%d\n", p->classname, extwin);
        p->atom_classname = XInternAtom(display, p->classname, False);

/*
        sprintf(buf, "%s%s", p->classname, XAUX_SOWIN_SUFFIX);
*/
        sprintf(buf, "%s", XAUX_OBJECT_CLASS_NAME);
        p->atom_sowin = XInternAtom(display, buf, False);

        sprintf(buf, "%s%s", p->classname, XAUX_EXTWIN_SUFFIX);
        p->atom_extwin = XInternAtom(display, buf, False);

        for (i = 0; i < p->atom_sx_num; i++) {
                sprintf(buf, "%s%s_%d", p->classname, XAUX_SX_SUFFIX, i);
                p->atom_sx[i] = XInternAtom(display, buf, False);
        }

        p->atom_sx_idx = 1;

        for (i = 0; i < p->atom_xs_num; i++) {
                sprintf(buf, "%s%s_%d", p->classname, XAUX_XS_SUFFIX, i);
                p->atom_xs[i] = XInternAtom(display, buf, False);
        }

        p->atom_xs_idx = 1;

        p->sowin = (Window)0;

        p->extwin = extwin;

        if (XGetSelectionOwner(display, p->atom_extwin) != None) {
                DEBUG_printf("%s: %s already exists.[%s](1)\n",
                        ME_EXT, ME_EXT, p->classname);
                return False;
        }

        XSetSelectionOwner(display, p->atom_extwin, p->extwin, CurrentTime);

        if (XGetSelectionOwner(display, p->atom_extwin) != p->extwin) {
                DEBUG_printf("%s: %s already exists.[%s](2)\n",
                        ME_EXT, ME_EXT, p->classname);
                XDestroyWindow(display, p->extwin);
                p->extwin = (Window)0;
                return False;
        }

        /* process sx properties which has been put on sowin before
           extwin is prepared */
        if (xaux_ext_get_sowin(p, display) == True) {
                for (i = p->atom_sx_idx; i < p->atom_sx_num; i++) {
                        if (xaux_ext_process_property_update(
                                display, p->sowin, p->atom_sx[i], p->atom_sx[i]) == False) {
                                break;
                        }
                }
        }
        return True;
}

#define _NET_WM_STATE_REMOVE        0    /* remove/unset property */
#define _NET_WM_STATE_ADD           1    /* add/set property */
#define _NET_WM_STATE_TOGGLE        2    /* toggle property  */

/* To fix bug 4765026: input method has a window in task list of Gnome desktop */
void xaux_ext_skip_taskbar_hint(Display *display, Window window, Bool add)
{
        static Atom net_wm_state_skip_taskbar = (Atom)0;

        if (!net_wm_state_skip_taskbar) {
                net_wm_state_skip_taskbar = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", True);
        }

        if (net_wm_state_skip_taskbar != None) {
                XEvent xev;
                xev.xclient.type = ClientMessage;
                xev.xclient.serial = 0;
                xev.xclient.send_event = True;
                xev.xclient.window = window;
                xev.xclient.message_type = XInternAtom (display, "_NET_WM_STATE", True);
                xev.xclient.format = 32;
                xev.xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
                xev.xclient.data.l[1] = net_wm_state_skip_taskbar;
                xev.xclient.data.l[2] = net_wm_state_skip_taskbar;

                XSendEvent (display, RootWindow(display, DefaultScreen(display)),
                                False,
                                SubstructureRedirectMask | SubstructureNotifyMask,
                                &xev);
        }
}

static Bool
xaux_ext_get_sowin(
        xaux_class_t	*xc,
        Display		*display)
{
        xc->sowin = XGetSelectionOwner(display, xc->atom_sowin);

        if (xc->sowin == None)
                return False;
        else
                return True;
}

static char *xaux_ext_compose_aux_ext_data(xaux_class_t *xc, aux_ext_data_t *aux_ext_data, int *string_len)
{
        char	*string_buf = NULL;
        size_t	i;
        size_t	total = 0;
        int	*ip;
        char	*sp;
        Bool	rv = True;

        total = XS_SIZE_PROP_HEADER_SETVALUE
                + (sizeof (CARD32) * aux_ext_data->integer_count);

        if (aux_ext_data->string_count > 0) {
                for (i = 0; i < aux_ext_data->string_count; i++) {
                        size_t	len;

                        len = aux_ext_data->string_list[i].length
                                * sizeof (CARD16);
                        total += ((sizeof (CARD16) + len + 3) / 4) * 4;
                }

                /*
                 * "+1" is required by mb_utf16() method.
                 * The method uses the area for BOM.
                 */
                total += sizeof (CARD16);
        }

        string_buf = (char *)malloc(total);
        if (string_buf == NULL) {
                return NULL;
        }

        XS_PROP_ATOM_AUX_NAME(string_buf) = xc->atom_classname;
        XS_PROP_IMID(string_buf) = aux_ext_data->im;
        XS_PROP_ICID(string_buf) = aux_ext_data->ic;

        XS_PROP_INT_COUNT(string_buf) = aux_ext_data->integer_count;
        XS_PROP_STR_COUNT(string_buf) = aux_ext_data->string_count;

        ip = (int *)XS_PROP_INT_LIST(string_buf);

        if (aux_ext_data->integer_count > 0) {
                for (i = 0; i < aux_ext_data->integer_count; i++) {
                        *ip++ = aux_ext_data->integer_list[i];
                }
        }

        sp = (char *)XS_PROP_STR_LIST(string_buf);

        if (aux_ext_data->string_count > 0) {
                char *		ob;
                size_t		obl;

                ob = sp;

                for (i = 0; i < aux_ext_data->string_count; i++) {
                        size_t		len;
                        int		pn;
                        unsigned char	*p;
                        size_t		j;

                        len = aux_ext_data->string_list[i].length;
                        p = aux_ext_data->string_list[i].ptr;

                        *(CARD16 *)ob = len;
                        ob += sizeof (CARD16);

                        for (j = 0; j < len; j++) {
                                *ob++ = *p++;
                        }

                        pn = padding[(sizeof (CARD16) + len) % 4];

                        /* padding */
                        for (j = 0; j < pn; j++) {
                                *ob++ = 0U;
                        }
                        sp = ob;
                }
        }

        *string_len = sp - &(string_buf[0]);
        return (string_buf);
}

static aux_ext_data_t *xaux_ext_decompose_from_string(xaux_class_t *xc, unsigned char *string_buf)
{
        int		i;
        XPoint		point;
        aux_ext_data_t	*aux_ext_data;

        aux_ext_data = (aux_ext_data_t *)calloc(1, sizeof(aux_ext_data_t));
        if (aux_ext_data == NULL)
                return NULL;

        /* header */

        aux_ext_data->type = AUX_EXT_DATA_DRAW;
        aux_ext_data->im = SX_PROP_IMID(string_buf);
        aux_ext_data->ic = SX_PROP_ICID(string_buf);
        aux_ext_data->aux_index = xc->index;
        aux_ext_data->aux_name = (unsigned char *)xc->classname;
        aux_ext_data->aux_name_length =
                strlen((const char *)aux_ext_data->aux_name);

        /* int values */

        aux_ext_data->integer_count = SX_PROP_INT_COUNT(string_buf);

        if (aux_ext_data->integer_count > 0) {
                aux_ext_data->integer_list =
                        (int *)SX_PROP_INT_LIST(string_buf);
        } else {
                aux_ext_data->integer_list = NULL;
        }

        /* string values */

        aux_ext_data->string_count = SX_PROP_STR_COUNT(string_buf);

        DEBUG_printf("aux_ext_data->integer_count: %d\n", aux_ext_data->integer_count);
        DEBUG_printf("aux_ext_data->string_count: %d\n", aux_ext_data->string_count);
        if (aux_ext_data->string_count > 0) {
                unsigned char * prop_str = SX_PROP_STR_LIST(string_buf);

                if ((aux_ext_data->string_list =
                        (aux_ext_string_t *)malloc(sizeof (aux_ext_string_t) *
                                aux_ext_data->string_count)) == NULL) {
                                free ((char *)aux_ext_data);
                                return NULL;
                }

                for(i = 0; i < aux_ext_data->string_count; i++) {
                        char *		ib;
                        size_t		ibl;

                        /* assign length of a string to ibl */
                        ibl = (size_t)*((CARD16 *)(prop_str));
                        /* move prop_str to point head of the string */
                        prop_str += sizeof(CARD16);
                        /* assign head of the string to ib */
                        ib = (char *)prop_str;
                        /* move prop_str to point lenght of next string */
                        prop_str += (ibl + padding[(sizeof(CARD16) + ibl) % 4]);

                        aux_ext_data->string_list[i].ptr = (unsigned char *)ib;
                        aux_ext_data->string_list[i].length = ibl;
                }
        } else {
                aux_ext_data->string_list = NULL;
        }

        aux_ext_data->string_ptr = NULL;

        aux_ext_data->clientwin = SX_PROP_CLIENTWIN(string_buf);
        aux_ext_data->point.x = SX_PROP_POSX(string_buf);
        aux_ext_data->point.y = SX_PROP_POSY(string_buf);
        aux_ext_data->focuswin = SX_PROP_FOCUSWIN(string_buf);

        return (aux_ext_data);
}

/************************************************************
    Send messages to aux object
************************************************************/
static Bool
xaux_ext_send_message(
        Display *	display,
        xaux_class_t *	xc,
        int		im_id,
        int		ic_id,
        aux_ext_data_type_t	type,
        Atom		atom)
{
        XClientMessageEvent	event;

        if (xaux_ext_get_sowin(xc, display) == False) {
                return False;
        }

        event.type = ClientMessage;
        event.serial = 0;
        event.send_event = True;
        event.display = display;
        event.window = xc->sowin;
        event.message_type = xc->atom_xs[0];
        event.format = 32;

        event.data.l[0] = xc->atom_classname;
        event.data.l[1] = ((CARD16)im_id << 8) | ((CARD16)ic_id & 0xffff);
        event.data.l[2] = xc->index;
        event.data.l[3] = type;
        if (type == AUX_EXT_DATA_SETVALUE) {
                event.data.l[4] = atom;
        } else {
                event.data.l[4] = 0; /* unused */
        }

        XSendEvent(display, xc->sowin, True, 0, (XEvent *)(&event));

        XFlush(display);

        return True;
}

static Bool
xaux_ext_send_property(
        Display *		display,
        xaux_class_t *		xc,
        int			im_id,
        int			ic_id,
        const unsigned char *	p,
        int			len)
{
        DEBUG_printf("xaux_ext_send_property: xc: %p, len: %d\n", xc, len);

        if (xaux_ext_get_sowin(xc, display) == False) {
                return False;
        }

        XChangeProperty(display, xc->sowin,
                xc->atom_xs[xc->atom_xs_idx], XA_STRING,
                8, PropModeReplace, (unsigned char *)p, len);

        if (xaux_ext_send_message(display, xc, im_id, ic_id,
                AUX_EXT_DATA_SETVALUE, xc->atom_xs[xc->atom_xs_idx]) == False) {
                        return False;
        }

        /* XFlush() has been called in xaux_ext_send_message() */

        if (++xc->atom_xs_idx == xc->atom_xs_num)
                xc->atom_xs_idx = 1;

        return True;
}

Bool
xaux_ext_SetValue(
        Display		*display,
        xaux_class_t	*xc,
        aux_ext_data_t	*aux_ext_data)
{
    char  *string_buf = NULL;
    int	  string_len;
    Bool  rv = True;

    aux_ext_data_t* aedata = NULL;

    if (aux_ext_data == NULL || xc == NULL) {
        return True;
    }

    DEBUG_printf("ext_SetValue[%s] im:0x%x ic:0x%x in=%d sn=%d\n",
                  xc->classname, aux_ext_data->im, aux_ext_data->ic,
                  aux_ext_data->integer_count, aux_ext_data->string_count);

    if (aux_ext_data->integer_count != 0 ||aux_ext_data->string_count != 0) {

        aedata = convertAuxExtData(aux_ext_data, 1);  //utf8->utf16
        string_buf = (char *)xaux_ext_compose_aux_ext_data(xc, aedata, &string_len);

        if (string_buf) {
            rv = xaux_ext_send_property(display, xc,
                                        aedata->im, aedata->ic,
                                        (unsigned char *)string_buf, string_len);

            free(string_buf);
        } else
            rv = True;
        freeConvertedAuxExtData(aedata);
    }

    return (rv);
}

/************************************************************
    Receive messages from aux object
************************************************************/
static Bool
xaux_ext_process_property_update(
        Display	*		display,
        Window			window,
        Atom			message_type,
        Atom			atom)
{
        Atom		actual_type_return;
        int		actual_format_return;
        unsigned long	nitem_return;
        unsigned long	bytes_after_return;
        unsigned char *	prop_return;
        int		r;
        Bool		rv;
        int		type;
        aux_ext_data_t 	*aux_ext_data, *aedata;
        xaux_class_t	*xc = &xaux_class;

        DEBUG_printf("xaux_ext_process_property_update ===================\n");
        if (window != xc->extwin && window != xc->sowin) {
                return False;
        }

        r = XGetWindowProperty(display, window,
                               atom, 0, INT_MAX, False,
                               AnyPropertyType, &actual_type_return,
                               &actual_format_return, &nitem_return,
                               &bytes_after_return, &prop_return);

        if (r != Success || actual_type_return == 0) {
                return False;
        }

        aux_ext_data = xaux_ext_decompose_from_string(xc, prop_return);
        if (aux_ext_data == NULL) {
                XFree(prop_return);
                return False;
        }

        type = SX_PROP_TYPE(prop_return);

        aedata = convertAuxExtData(aux_ext_data, 0); //UTF16->UTF8

        if (type == AUX_EXT_DATA_START) {
            DEBUG_printf("[%s] received START via property\n", xc->classname);
            rv = xaux_ext_Start(xc, aedata, message_type);
        } else if (type == AUX_EXT_DATA_DONE) {
            DEBUG_printf("[%s] received DONE via property\n", xc->classname);
            rv = xaux_ext_Done(xc, aedata, message_type);
        } else if (type == AUX_EXT_DATA_DRAW) {
            DEBUG_printf("[%s] received DRAW via property\n", xc->classname);
            rv = xaux_ext_Draw(xc, aedata, message_type);
        }
        freeConvertedAuxExtData(aedata);

        free(aux_ext_data->string_list);
        free(aux_ext_data);
        XFree(prop_return);

        return rv;
}

Bool
xaux_ext_process_client_message(
        Display	*		display,
        XClientMessageEvent *	event)
{
        aux_ext_data_t	aux_ext_data_;
        aux_ext_data_t	*aux_ext_data = &(aux_ext_data_);
        aux_ext_data_type_t	type;
        xaux_class_t	*xc = &xaux_class;

        if (event->data.l[0] != xc->atom_classname) {
                return False;
        }

        if (event->window != xc->extwin) {
                return False;
        }

        aux_ext_data->im = ((CARD32)(event->data.l[1])) >> 16;
        aux_ext_data->ic = ((CARD32)(event->data.l[1])) & 0xffff;
        aux_ext_data->aux_index = (CARD32)(event->data.l[2]);

        type = (CARD32)(event->data.l[3]);

        switch (type) {
        case AUX_EXT_DATA_START:
                DEBUG_printf("[%s] received START via ClientMessage\n",
                        xc->classname);
                return xaux_ext_Start(xc, aux_ext_data,(Atom)(CARD32)(event->message_type));
                break;
        case AUX_EXT_DATA_DRAW:
                DEBUG_printf("[%s] notified DRAW via ClientMessage\n",
                        xc->classname);
                return xaux_ext_process_property_update(
                        display, xc->extwin, (Atom)(CARD32)(event->message_type), (Atom)(CARD32)(event->data.l[4]));
        case AUX_EXT_DATA_DONE:
                DEBUG_printf("[%s] received DONE via ClientMessage\n",
                        xc->classname);
                return xaux_ext_Done(xc, aux_ext_data,(Atom)(CARD32)(event->message_type));
                break;
        default:
                return False;
        }
}

