#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <Xm/Xm.h>
#include <Xm/RowColumn.h>
#include <Xm/DrawnB.h>

#include <XmHTML/XmHTML.h>
#include <XmHTML/XmHTMLP.h>
#include <XmHTML/XmHTMLI.h>

static void DrawImage(Widget w, XmImage *image, Boolean from_timerCB);
static void DrawFrame(Widget w, XmImage *image, int xs, int ys);
static void ResizeExpose(Widget w, XtPointer client_data, XtPointer call_data);

#define APP_CLASS               "HTMLDemos"

typedef struct _TimeOutData {
    Widget w;
    XmImage *image;
} TimeOutData;

int main(int argc, char **argv)
{
    XtAppContext context;
    Widget toplevel, rc, button;
    XmImage *working_image = NULL;
    XmImageConfig config;
    String working_image_file;
    int i;
    unsigned char img_type;

    fprintf(stderr, "%s, %i\n", XmHTMLVERSION_STRING, XmHTMLGetVersion());

    if (argv[1]) working_image_file = strdup(argv[1]);

    if (!working_image_file)
    {
        printf("You must supply a filename.\n");
        exit(1);
    }

    /* create toplevel widget */
    toplevel = XtVaAppInitialize(&context, APP_CLASS, NULL, 0,
		                 &argc, argv, NULL, NULL, NULL);

    /* why not create a container */
    rc = XtVaCreateManagedWidget(NULL, xmRowColumnWidgetClass, 
                                 toplevel, NULL);

    /* create the image(s) */
    config.ncolors = 64;
    config.flags = ImageQuantize|ImageFSDither;
    working_image = XmImageCreate(rc, working_image_file, 0, 0, &config);
    if (! working_image)
    {
        printf("Unable to create image - %s.\n", working_image_file);
        exit(1);
    }


    button = XtVaCreateManagedWidget(NULL,
                                     xmDrawnButtonWidgetClass, rc,
                                     XmNpushButtonEnabled, True,
                                     XmNhighlightThickness, 0,
                                     XmNwidth, working_image->width+8,
                                     XmNheight, working_image->height+8,
	                             NULL);

    /* realize everything */
    XtRealizeWidget(toplevel);

    /* set ready label */
    img_type = XmHTMLImageGetType(working_image_file, NULL, 0);

    if(img_type != IMAGE_GIFANIM && img_type != IMAGE_GIFANIMLOOP)
    {
        XtAddCallback(button, XmNexposeCallback, ResizeExpose, 
                      (XtPointer)working_image);
    }
    else DrawImage(button, working_image, True);

    /* enter the event loop */
    XtAppMainLoop(context);

    /* never reached, but keeps compiler happy */
    exit(EXIT_SUCCESS);

}


/*
* To prevent racing conditions, we must first remove an
* existing timeout proc before we add a new one.
*/
#define REMOVE_TIMEOUTPROC(IMG) { \
	if(IMG->proc_id) \
	{ \
		XtRemoveTimeOut(IMG->proc_id); \
		IMG->proc_id = None; \
	} \
}

static void
TimerCB(XtPointer data, XtIntervalId *id)
{
    TimeOutData *info = (TimeOutData*)data;

    info->image->options |= IMG_FRAMEREFRESH;
    DrawImage(info->w, info->image, True);
}

#define RESET_GC(MYGC) { \
	values.clip_mask = None; \
	values.clip_x_origin = 0; \
	values.clip_y_origin = 0; \
	valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin; \
	XChangeGC(dpy, MYGC, valuemask, &values); \
}

static void
DrawImage(Widget w, XmImage *image, Boolean from_timerCB)
{
    int xs, ys;
    Display *dpy = XtDisplay(w);
    Window win = XtWindow(w);
    GC gc = XDefaultGCOfScreen(XtScreen(w));
    XGCValues values;
    unsigned long valuemask;
    unsigned short wi, h;

    XtVaGetValues(w, XmNwidth, &wi, XmNheight, &h, NULL);

    /* compute correct image offsets */
    xs = (wi - image->width)/2;
    ys = (h - image->height)/2;

    if (ImageFrameRefresh(image))
    {
        if (xs + image->width < 0 || ys + image->height < 0)
        {
            REMOVE_TIMEOUTPROC(image);
            return;
        }
    }

    /*
    * If this is an animation, paint next frame or restore current
    * state when we are scrolling this animation on and off screen.
    */
    
    DrawFrame(w, image, xs, ys);

    /* reset gc */
    RESET_GC(gc);
}

static void ResizeExpose(Widget w, XtPointer client_data, XtPointer call_data)
{

    XmImage *image = (XmImage *)client_data;
    int xs, ys;
    unsigned short wi, h;

    XtVaGetValues(w, XmNwidth, &wi, XmNheight, &h, NULL);

    /* compute correct image offsets */
    xs = (wi - image->width)/2;
    ys = (h - image->height)/2;

    XCopyArea(XtDisplay(w), image->pixmap, XtWindow(w), 
              XDefaultGCOfScreen(XtScreen(w)), 0, 0, image->width, 
              image->height, xs, ys);
}


static void
DrawFrame(Widget w, XmImage *image, int xs, int ys)
{
    int idx, width = 0, height = 0, fx, fy;
    unsigned long valuemask;
    XGCValues values;
    Display *dpy = XtDisplay(w);
    Window win = XtWindow(w);
    GC gc = XDefaultGCOfScreen(XtScreen(w));
    Pixel bg, fg;
    TimeOutData *data = XtNew(TimeOutData);

    XtVaGetValues(w, XmNbackground, &bg, XmNforeground, &fg, NULL);
    data->w = w;
    data->image = image;

    /* first reset the gc */
    RESET_GC(gc);

    /*
    * First check if we are running this animation internally. If we aren't
    * we have a simple animation of which each frame has the same size and
    * no disposal method has been specified. This type of animations are blit
    * to screen directly.
    */
    if(!ImageHasState(image))
    {
        /* index of current frame */
        idx = image->current_frame;
        width  = image->frames[idx].w;
        height = image->frames[idx].h;

        /* can happen when a frame falls outside the logical screen area */
        if(image->frames[idx].pixmap != None)
        {
            /* plug in the clipmask */
            if(image->frames[idx].clip)
            {
                values.clip_mask = image->frames[idx].clip;
                values.clip_x_origin = xs;
                values.clip_y_origin = ys;
                valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin;
                XChangeGC(dpy, gc, valuemask, &values);
            }
            /* blit frame to screen */
            XCopyArea(dpy, image->frames[idx].pixmap, win, gc, 0, 0, width,
                      height, xs, ys);
        }
        /*
        * Jump to frame updating when we are not triggered
        * by an exposure, otherwise just return.
        */
        if(ImageFrameRefresh(image)) goto nextframe;
        return;
    }
    /*
    * If DrawFrame was triggered by an exposure, just blit current animation
    * state to screen and return immediatly.
    */
    if(!ImageFrameRefresh(image))
    {
        XCopyArea(dpy, image->pixmap, win, gc, 0, 0, image->width,
        image->height, xs, ys);
        return;
    }

    idx = image->current_frame ? image->current_frame - 1 : image->nframes - 1;
    
    if(image->frames[idx].pixmap != None)
    {
        fx     = image->frames[idx].x;
        fy     = image->frames[idx].y;
        width  = image->frames[idx].w;
        height = image->frames[idx].h;
    
        if(image->frames[idx].dispose == XmIMAGE_DISPOSE_BY_BACKGROUND)
        {
            XSetForeground(dpy, gc, bg);
            XFillRectangle(dpy, image->pixmap, gc, fx, fy, width, height);
            XSetForeground(dpy, gc, fg);
        }
        else if(image->frames[idx].dispose == XmIMAGE_DISPOSE_NONE &&
                idx == 0 && image->frames[idx].clip != None)
        {
            XSetForeground(dpy, gc, bg);
            XFillRectangle(dpy, image->pixmap, gc, fx, fy, width, height);
            XSetForeground(dpy, gc, fg);

            /* now plug in the clipmask */
            values.clip_mask = image->frames[idx].clip;
            values.clip_x_origin = fx;
            values.clip_y_origin = fy;
            valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin;
            XChangeGC(dpy, gc, valuemask, &values);

            /* paint it. Use full image dimensions */
            XCopyArea(dpy, image->frames[idx].pixmap, image->pixmap, gc,
                      0, 0, width, height, fx, fy);
        }
        /* dispose by previous (the only one to have a prev_state) */
        else if(image->frames[idx].prev_state != None)
        {
            /* plug in the clipmask */
            if(image->frames[idx].clip)
            {
                /* set gc values */
                values.clip_mask = image->frames[idx].clip;
                values.clip_x_origin = fx;
                values.clip_y_origin = fy;
                valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin;
                XChangeGC(dpy, gc, valuemask, &values);
            }
            /* put previous screen state on current state */
            XCopyArea(dpy, image->frames[idx].prev_state, image->pixmap, gc,
                      0, 0, width, height, fx, fy);
        }
    }
    /* reset gc */
    RESET_GC(gc);

    /* index of current frame */
    idx = image->current_frame;
    
    /* can happen when a frame falls outside the logical screen area */
    if(image->frames[idx].pixmap != None)
    {
        fx      = image->frames[idx].x;
        fy      = image->frames[idx].y;
        width   = image->frames[idx].w;
        height  = image->frames[idx].h;
        
        /*
        * get current screen state if we are to dispose of this frame by the 
        * previous state. The previous state is given by the current pixmap,
        * so we just create a new pixmap and copy the current one into it.
        * This is about the fastest method I can think of.
        */
        if(image->frames[idx].dispose == XmIMAGE_DISPOSE_BY_PREVIOUS &&
           image->frames[idx].prev_state == None)
        {
            Pixmap prev_state;
            GC tmpGC;
    
            /* create pixmap that is to receive the image */
            /* NEEDS to get depth of screen !!! */
            prev_state = XCreatePixmap(dpy, win, width, height, 
                                       DefaultDepthOfScreen(XtScreen(w)));
    
            /* copy it */
            tmpGC = XCreateGC(dpy, prev_state, 0, 0);
            XSetFunction(dpy, tmpGC, GXcopy);
            XCopyArea(dpy, image->pixmap, prev_state, tmpGC, fx, fy, width,
                      height, 0, 0);

            /* and save it */
            image->frames[idx].prev_state = prev_state;

            /* free and destroy */
            XFreeGC(dpy, tmpGC);
        }
        if(image->frames[idx].clip)
        {
            values.clip_mask = image->frames[idx].clip;
            values.clip_x_origin = fx;
            values.clip_y_origin = fy;
            valuemask = GCClipMask | GCClipXOrigin | GCClipYOrigin;
            XChangeGC(dpy, gc, valuemask, &values);
        }
        XCopyArea(dpy, image->frames[idx].pixmap, image->pixmap, gc, 0, 0,
                  width, height, fx, fy);

        /* reset gc */
        RESET_GC(gc);

        /* blit current state to screen */
        XCopyArea(dpy, image->pixmap, win, gc, 0, 0, image->width,
                  image->height, xs, ys);
    }
nextframe:
    image->current_frame++;

    /* will get set again by TimerCB */
    image->options &= ~(IMG_FRAMEREFRESH);
    
    if(image->current_frame == image->nframes)
    {
        image->current_frame = 0;
        if(image->loop_count)
        {
            image->current_loop++;
            if(image->current_loop == image->loop_count)
                image->options &= ~(IMG_ISANIM);
        }
    }
    REMOVE_TIMEOUTPROC(image);

    image->proc_id = XtAppAddTimeOut(image->context, 
                                     image->frames[idx].timeout, TimerCB, 
                                     data);

}

