#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>

#include <X11/X.h>
#include <X11/Intrinsic.h>
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/DrawingA.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/ToggleB.h>
#include <Xm/Scale.h>
#include <Xm/Separator.h>
#include <Xm/Text.h>
#include <Xm/SelectioB.h>

#include "RegEdit.h"
#include "ida.h"
#include "x11.h"
#include "readers.h"
#include "viewer.h"
#include "color.h"
#include "lut.h"

/* ---------------------------------------------------------------------- */

#define HIST_SIZE 60

struct ida_coledit;

struct ida_hist {
    /* x11 */
    GC                     gc;
    unsigned long          color;

    /* histogram */
    Widget                 hist;
    unsigned int           max;
    unsigned int           data[256];

    /* mapping */
    Widget                 map;
    struct op_map_parm_ch  parm;

    struct ida_coledit      *up;
};

struct ida_coledit {
    /* misc */
    Widget dlg,form,vals,toggle;
    Widget l,r,t,b,g;
    int lock,apply;
    
    /* histogram data */
    struct ida_hist red;
    struct ida_hist green;
    struct ida_hist blue;
    struct ida_hist *cur;
};

/* ---------------------------------------------------------------------- */

static void
color_calchist(struct ida_image *img, struct ida_coledit *me)
{
    unsigned char *pix;
    unsigned int i,x,y,max;

    pix = img->data;
    for (y = 0; y < img->i.height; y++) {
	for (x = 0; x < img->i.width; x++) {
	    me->red.data[pix[0]]++;
	    me->green.data[pix[1]]++;
	    me->blue.data[pix[2]]++;
	    pix += 3;
	}
    }
    max = 0;
    for (i = 0; i < 256; i++) {
	if (max < me->red.data[i])
	    max = me->red.data[i];
	if (max < me->green.data[i])
	    max = me->green.data[i];
	if (max < me->blue.data[i])
	    max = me->blue.data[i];
    }
    me->red.max   = max;
    me->green.max = max;
    me->blue.max  = max;
}

static void
color_update(struct ida_coledit *me, struct ida_hist *h, int text)
{
    struct op_map_parm param;
    char tmp[32];

    if (me->lock) {
	if (&me->red != h)
	    me->red.parm = h->parm;
	if (&me->green != h)
	    me->green.parm = h->parm;
	if (&me->blue != h)
	    me->blue.parm = h->parm;
	XClearArea(XtDisplay(me->red.hist), XtWindow(me->red.hist),
		   0,0,0,0, True);
	XClearArea(XtDisplay(me->red.map), XtWindow(me->red.map),
		   0,0,0,0, True);
	XClearArea(XtDisplay(me->green.hist), XtWindow(me->green.hist),
		   0,0,0,0, True);
	XClearArea(XtDisplay(me->green.map), XtWindow(me->green.map),
		   0,0,0,0, True);
	XClearArea(XtDisplay(me->blue.hist), XtWindow(me->blue.hist),
		   0,0,0,0, True);
	XClearArea(XtDisplay(me->blue.map), XtWindow(me->blue.map),
		   0,0,0,0, True);
    } else {
	XClearArea(XtDisplay(h->hist), XtWindow(h->hist),
		   0,0,0,0, True);
	XClearArea(XtDisplay(h->map), XtWindow(h->map),
		   0,0,0,0, True);
    }
    if ((me->lock || h == me->cur) && text >= 1) {
	/* mouse-click updateable values */
	sprintf(tmp,"%d",h->parm.left);
	XmTextSetString(me->l,tmp);
	sprintf(tmp,"%d",h->parm.right);
	XmTextSetString(me->r,tmp);
    }
    if ((me->lock || h == me->cur) && text >= 2) {
	/* others */
	sprintf(tmp,"%d",h->parm.bottom);
	XmTextSetString(me->b,tmp);
	sprintf(tmp,"%d",h->parm.top);
	XmTextSetString(me->t,tmp);
	sprintf(tmp,"%.2f",h->parm.gamma);
	XmTextSetString(me->g,tmp);
    }
    
    param.red   = me->red.parm;
    param.green = me->green.parm;
    param.blue  = me->blue.parm;
    viewer_start_preview(ida,&desc_map,&param);
}

static void
color_drawmap(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_hist *me = client_data;
    XmDrawingAreaCallbackStruct *cb = calldata;
    XGCValues values;
    int left,right,top,bottom,i,val,x1,y1,x2,y2;
    float p;

    if (cb->reason == XmCR_EXPOSE) {
	/* window needs redraw */
	XExposeEvent *e = (XExposeEvent*)cb->event;
	if (e->count)
	    return;
	values.foreground = x11_gray;
	XChangeGC(dpy,me->gc,GCForeground,&values);
	left   = me->parm.left   * HIST_SIZE / 255;
	right  = me->parm.right  * HIST_SIZE / 255;
	bottom = me->parm.bottom * HIST_SIZE / 255;
	top    = me->parm.top    * HIST_SIZE / 255;
	if (me->parm.left > 0)
	    XFillRectangle(dpy,XtWindow(me->map),me->gc,
			   0,0,left,HIST_SIZE);
	if (me->parm.right < 255)
	    XFillRectangle(dpy,XtWindow(me->map),me->gc,
			   right,0,HIST_SIZE-right,HIST_SIZE);
	values.foreground = me->color;
	XChangeGC(dpy,me->gc,GCForeground,&values);
	if (me->parm.left > 0)
	    XDrawLine(dpy,XtWindow(me->map),me->gc,
		      0,HIST_SIZE-bottom,left,HIST_SIZE-bottom);
	if (me->parm.right < 255)
	    XDrawLine(dpy,XtWindow(me->map),me->gc,
		      right,HIST_SIZE-top,HIST_SIZE,HIST_SIZE-top);
	p = 1/me->parm.gamma;
	x2 = y2 = 0;
	for (i = left; i <= right; i++) {
	    val  = pow((float)(i-left)/(right-left),p) * (top-bottom) + 0.5;
	    val += bottom;
	    if (val < 0)         val = 0;
	    if (val > HIST_SIZE) val = HIST_SIZE;
	    x1 = x2;
	    y1 = y2;
	    x2 = i;
	    y2 = HIST_SIZE-val;
	    if (i > left)
		XDrawLine(dpy,XtWindow(me->map),me->gc,
			  x1,y1,x2,y2);
	}
    }
}

static void
color_drawhist(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_hist *me = client_data;
    XmDrawingAreaCallbackStruct *cb = calldata;
    XGCValues values;
    int i,val;

    if (cb->reason == XmCR_EXPOSE) {
	/* window needs redraw */
	XExposeEvent *e = (XExposeEvent*)cb->event;
	if (e->count)
	    return;
	values.foreground = x11_gray;
	XChangeGC(dpy,me->gc,GCForeground,&values);
	if (me->parm.left > 0)
	    XFillRectangle(dpy,XtWindow(me->hist),me->gc,
			   0,0,me->parm.left,HIST_SIZE);
	if (me->parm.right < 255)
	    XFillRectangle(dpy,XtWindow(me->hist),me->gc,
			   me->parm.right,0,256-me->parm.right,HIST_SIZE);
	values.foreground = me->color;
	XChangeGC(dpy,me->gc,GCForeground,&values);
	for (i = 0; i < 256; i++) {
	    val = log(me->data[i])*HIST_SIZE/log(me->max);
	    XDrawLine(dpy,XtWindow(me->hist),me->gc,
		      i,HIST_SIZE,i,HIST_SIZE-val);
	}
    }
}

static void
color_mouse(Widget widget, XtPointer client_data,
	    XEvent *ev, Boolean *cont)
{
    struct ida_hist *me = client_data;
    int x,y;

    switch (ev->type) {
    case ButtonPress:
    case ButtonRelease:
    {
	XButtonEvent *e = (XButtonEvent*)ev;

	x = e->x;
	y = e->y;
	break;
    }
    case MotionNotify:
    {
	XMotionEvent *e = (XMotionEvent*)ev;

	x = e->x;
	y = e->y;
	break;
    default:
	return;
    }
    }
    if (x > (me->parm.right + me->parm.left)/2) {
	me->parm.right = x;
	if (me->parm.right > 255)
	    me->parm.right = 255;
	if (me->parm.right < me->parm.left)
	    me->parm.right = me->parm.left;
    } else {
	me->parm.left = x;
	if (me->parm.left < 0)
	    me->parm.left = 0;
	if (me->parm.left > me->parm.right)
	    me->parm.left = me->parm.right;
    }
    color_update(me->up,me,1);
}

static void
color_lock(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_coledit *me = client_data;
    XmToggleButtonCallbackStruct *cb = calldata;
    Widget label,button;

    label  = XmOptionLabelGadget(me->vals);
    button = XmOptionButtonGadget(me->vals);
    me->lock = cb->set;
    XtVaSetValues(label,XtNsensitive,!me->lock,NULL);
    XtVaSetValues(button,XtNsensitive,!me->lock,NULL);
}

static void
color_vals(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_hist *cur = client_data;
    struct ida_coledit *me = cur->up;

    me->cur = cur;
    color_update(me,cur,2);
}

static void
color_text(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_coledit *me = client_data;
    int left,right,bottom,top;
    float gamma;

    if (widget == me->l &&
	1 == sscanf(XmTextGetString(me->l),"%d",&left) &&
	left >= 0 && left <= me->cur->parm.right) {
	me->cur->parm.left = left;
    }
    if (widget == me->r &&
	1 == sscanf(XmTextGetString(me->r),"%d",&right) &&
	me->cur->parm.left <= right && right <= 255) {
	me->cur->parm.right = right;
    }
    if (widget == me->b &&
	1 == sscanf(XmTextGetString(me->b),"%d",&bottom) &&
	bottom <= me->cur->parm.top) {
	me->cur->parm.bottom = bottom;
    }
    if (widget == me->t &&
	1 == sscanf(XmTextGetString(me->t),"%d",&top) &&
	me->cur->parm.bottom <= top) {
	me->cur->parm.top = top;
    }
    if (widget == me->g &&
	1 == sscanf(XmTextGetString(me->g),"%f",&gamma)) {
	me->cur->parm.gamma = gamma;
    }
    color_update(me,me->cur,0);
}

static void
color_pick_ok(int x, int y, unsigned char *pix, XtPointer data)
{
    struct ida_coledit *me = data;
    int max;

    if (debug)
	fprintf(stderr,"color_pick_ok: +%d+%d %d/%d/%d\n",
		x,y, pix[0],pix[1],pix[2]);

    max = 0;
    if (max < pix[0])
	max = pix[0];
    if (max < pix[1])
	max = pix[1];
    if (max < pix[2])
	max = pix[2];

    XmToggleButtonSetState(me->toggle,False,True);
    me->red.parm.right   = (int)255 * pix[0] / max;
    color_update(me,&me->red,1);
    me->green.parm.right = (int)255 * pix[1] / max;
    color_update(me,&me->green,1);
    me->blue.parm.right  = (int)255 * pix[2] / max;
    color_update(me,&me->blue,1);

    if (debug)
	fprintf(stderr,"color_pick_ok: %d/%d/%d max=%d\n",
		me->red.parm.right,
		me->green.parm.right,
		me->blue.parm.right,
		max);
}

static void
color_pick(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_coledit *me = client_data;
    viewer_pick(ida,color_pick_ok,me);
}

static void
color_createhist(Widget parent, char *name, unsigned long color,
		 struct ida_hist *me)
{
    char tmp[32];

    sprintf(tmp,"h%s",name);
    me->hist = XtVaCreateManagedWidget(tmp,xmDrawingAreaWidgetClass,parent,
				       XtNwidth,256,
				       XtNheight,HIST_SIZE,
				       NULL);
    sprintf(tmp,"m%s",name);
    me->map = XtVaCreateManagedWidget(tmp,xmDrawingAreaWidgetClass,parent,
				      XtNwidth,HIST_SIZE,
				      XtNheight,HIST_SIZE,
				      NULL);
    XtAddEventHandler(me->hist,
		      ButtonPressMask   |
		      ButtonReleaseMask |
		      ButtonMotionMask,
		      False,color_mouse,me);
    XtAddCallback(me->hist,XmNexposeCallback,color_drawhist,me);
    XtAddCallback(me->map,XmNexposeCallback,color_drawmap,me);
    me->gc = XCreateGC(dpy,XtWindow(app_shell),0,NULL);
    me->color = color;
    me->parm = op_map_nothing;
}

static void
color_button_cb(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_coledit *me = client_data;
    XmSelectionBoxCallbackStruct *cb = calldata;

    if (cb->reason == XmCR_OK)
	me->apply = 1;
    XtDestroyWidget(XtParent(me->form));
}

static void
color_destroy(Widget widget, XtPointer client_data, XtPointer calldata)
{
    struct ida_coledit *me = client_data;
    struct op_map_parm param;

    if (me->apply) {
	param.red   = me->red.parm;
	param.green = me->green.parm;
	param.blue  = me->blue.parm;
	viewer_start_op(ida,&desc_map,&param);
    } else
	viewer_cancel_preview(ida);
    viewer_unpick(ida);
    free(me);
}

void
color_init(struct ida_image *img)
{
    Widget menu,push,rc;
    struct ida_coledit *me;
    Arg args[2];

    me = malloc(sizeof(*me));
    memset(me,0,sizeof(*me));
    color_calchist(img,me);

    /* dialog shell */
    me->dlg = XmCreatePromptDialog(app_shell,"color",NULL,0);
    XmdRegisterEditres(XtParent(me->dlg));
    XtUnmanageChild(XmSelectionBoxGetChild(me->dlg,XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(me->dlg,XmDIALOG_HELP_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(me->dlg,XmDIALOG_TEXT));
    me->form = XtVaCreateManagedWidget("form",xmFormWidgetClass,
				       me->dlg,NULL);
    XtAddCallback(XtParent(me->dlg),XmNdestroyCallback,color_destroy,me);
    XtAddCallback(me->dlg,XmNokCallback,color_button_cb,me);
    XtAddCallback(me->dlg,XmNcancelCallback,color_button_cb,me);

    /* histograms */
    XtVaCreateManagedWidget("hist",xmLabelWidgetClass,
			    me->form,NULL);
    color_createhist(me->form,"red",  x11_red,  &me->red);
    color_createhist(me->form,"green",x11_green,&me->green);
    color_createhist(me->form,"blue", x11_blue, &me->blue);
    me->red.up = me;
    me->green.up = me;
    me->blue.up = me;
    XtVaCreateManagedWidget("map",xmLabelWidgetClass,
			    me->form,NULL);

    /* control */
    me->toggle = XtVaCreateManagedWidget("lock",xmToggleButtonWidgetClass,
					 me->form,NULL);
    XtAddCallback(me->toggle,XmNvalueChangedCallback,color_lock,me);
    menu = XmCreatePulldownMenu(me->form,"valsM",NULL,0);
    XtSetArg(args[0],XmNsubMenuId,menu);
    me->vals = XmCreateOptionMenu(me->form,"vals",args,1);
    XtManageChild(me->vals);
    push = XtVaCreateManagedWidget("red",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,color_vals,&me->red);
    push = XtVaCreateManagedWidget("green",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,color_vals,&me->green);
    push = XtVaCreateManagedWidget("blue",xmPushButtonWidgetClass,menu,NULL);
    XtAddCallback(push,XmNactivateCallback,color_vals,&me->blue);

    /* in range */
    rc = XtVaCreateManagedWidget("in",xmRowColumnWidgetClass,me->form,NULL);
    XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc,NULL);
    me->l = XtVaCreateManagedWidget("left",xmTextWidgetClass,rc,NULL);
    XtAddCallback(me->l,XmNvalueChangedCallback,color_text,me);
    me->r = XtVaCreateManagedWidget("right",xmTextWidgetClass,rc,NULL);
    XtAddCallback(me->r,XmNvalueChangedCallback,color_text,me);

    /* out range */
    rc = XtVaCreateManagedWidget("out",xmRowColumnWidgetClass,me->form,NULL);
    XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc,NULL);
    me->b = XtVaCreateManagedWidget("bottom",xmTextWidgetClass,rc,NULL);
    XtAddCallback(me->b,XmNvalueChangedCallback,color_text,me);
    me->t = XtVaCreateManagedWidget("top",xmTextWidgetClass,rc,NULL);
    XtAddCallback(me->t,XmNvalueChangedCallback,color_text,me);

    /* gamma */
    rc = XtVaCreateManagedWidget("gamma",xmRowColumnWidgetClass,me->form,NULL);
    XtVaCreateManagedWidget("label",xmLabelWidgetClass,rc,NULL);
    me->g = XtVaCreateManagedWidget("gamma",xmTextWidgetClass,rc,NULL);
    XtAddCallback(me->g,XmNvalueChangedCallback,color_text,me);

    /* testing stuff */
    rc = XtVaCreateManagedWidget("pick",xmRowColumnWidgetClass,me->form,NULL);
    push = XtVaCreateManagedWidget("white",xmPushButtonWidgetClass,rc,NULL);
    XtAddCallback(push,XmNactivateCallback,color_pick,me);
    
    XtManageChild(me->dlg);

    me->cur = &me->red;
    color_update(me,me->cur,2);
    XmToggleButtonSetState(me->toggle,True,True);
}
