/*
 * Copyright 1995 TriloBYTE Software Solutions.
 *
 * Author: Amen Zwa, TriloBYTE.
 */



static char* sccsid = "%W%\t%G%\tAmen Zwa, TriloBYTE.";



// imagewnd.c: image window



#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include "imagewnd.h"
#include "menu.h"



XStandardColormap* smap;		// XA_RGB_*_MAP info

const MAXCOLOR =128;			// maximum colors supported by imgsvr

static char buf[STRLEN];



// activate callback procedure for image window
void cp_activate_imagewnd(Widget, XtPointer, XtPointer)
{
    Menu* menu = (Menu*) context->mainmenu;
    menu->menu_select(mklong(im_file_pdm, im_quit_itm));
}



// input procedure to accept client connection
void ip_accept_imagewnd(XtPointer, int*, XtInputId*)
{
    if (!imagewnd->cltflg) imagewnd->conn_client();
}



// input procedure to dispatch incoming message
void ip_dispatch_imagewnd(XtPointer, int*, XtInputId*)
{
    imagewnd->dispatch();
}



/**** Image ****/

Image::~Image()
{
    if (pmap) XFreePixmap(display, pmap);
    if (col) {
	for (int i = 0; i < MAXCOLOR; i++)
	    if (ownflg[i]) XFreeColors(display, colormap, col+i, 1, 0L);
	delete[] col;
	delete[] ownflg;
	delete[] rgbfname;
    }
}



void Image::init()
{
    WDrawingArea::init();
    pmap = XCreatePixmap(display, xwnd, width, height, colorpln);
    XSetForeground(display, dagc, colortbl[black_color]);
    XFillRectangle(display, pmap, dagc, 0, 0, width, height);
    // draw title
    ttlfont = WFont("-adobe-times-bold-r-*-*-14-*-*-*-*-*-*-*");
    strcpy(buf, "Image Server");
    int slen = strlen(buf);
    int swdt = XTextWidth(ttlfont.xfs, buf, slen);
    XPoint p = { (width - swdt) / 2, height / 3 };
    XSetForeground(display, dagc, colortbl[white_color]);
    XSetFont(display, dagc, ttlfont.xfs->fid);
    XDrawString(display, pmap, dagc, p.x, p.y, buf, slen);
    p.y += ttlfont.height();
    clear_text();
    draw_text(p.y, "Amen Zwa, TriloBYTE");
}



int Image::alloc_colmap(const char* fn)
{
    col = new Pixel[MAXCOLOR];
    memset(col, 0, MAXCOLOR * sizeof(Pixel));
    ifstream is(fn);
    if (!is) return -1;
    float r, g, b;
    for (int i = 0; is >> r >> g >> b && i < MAXCOLOR; i++) {
	XColor hwcol;
	hwcol.red = scale_color(r);
	hwcol.green = scale_color(g);
	hwcol.blue = scale_color(b);
	if (XAllocColor(display, colormap, &hwcol)) col[i] = hwcol.pixel;
    }
    return 0;
}



void Image::clear_text()
{
    if (!dagc) return;
    XSetForeground(display, dagc, colortbl[black_color]);
    XFillRectangle(display, pmap, dagc, 0, height/2, width, height);
}



void Image::draw_text(int y, const char* s)
{
    if (!dagc) return;
    int slen = strlen(s);
    int swdt = XTextWidth(font.xfs, s, slen);
    XSetForeground(display, dagc, colortbl[white_color]);
    XSetFont(display, dagc, font.xfs->fid);
    XDrawString(display, pmap, dagc, (width - swdt) / 2, y, s, slen);
    expose();
}



void Image::draw_image(const u_char* r, const u_char* g, const u_char* b)
{
    if (!dagc) return;
    XSetForeground(display, dagc, colortbl[black_color]);
    XFillRectangle(display, pmap, dagc, 0, 0, width, height);
    for (int x = 0; x < width; x++)
	for (int y = 0; y < height; y++) {
	    int ndx = x * width + y;
	    float fr = float(r[ndx]) / 255.0F;
	    float fg = float(g[ndx]) / 255.0F;
	    float fb = float(b[ndx]) / 255.0F;
	    u_long c = smap->base_pixel
	        + u_long(0.5F + fr * smap->red_max) * smap->red_mult
		+ u_long(0.5F + fg * smap->green_max) * smap->green_mult
		+ u_long(0.5F + fb * smap->blue_max) * smap->blue_mult;
	    XSetForeground(display, dagc, c);
	    XDrawPoint(display, pmap, dagc, x, y);
	}
#if defined(TEST)
    static int numframe = 0;
    ostrstream(buf,STRLEN) << "Image " << (numframe++) << ends;
    int slen = strlen(buf);
    int swdt = XTextWidth(font.xfs, buf, slen);
    XSetFont(display, dagc, font.xfs->fid);
    XSetForeground(display, dagc, colortbl[white_color]);
    XDrawString(display, pmap, dagc,
	(width - swdt) / 2, font.height(), buf, slen);
#endif
    expose();
}



void Image::draw_image(const u_char* c)
{
    if (!dagc) return;
    if (!col && rgbfname)
	if (alloc_colmap(rgbfname) == -1) {
	    ostrstream(buf,STRLEN) << "Cannot read RGB color file "
		<< rgbfname << '.' << ends;
	    error(buf);
	    exit(1);
	}
    XSetForeground(display, dagc, colortbl[black_color]);
    XFillRectangle(display, pmap, dagc, 0, 0, width, height);
    for (int x = 0; x < width; x++)
	for (int y = 0; y < height; y++) {
	    int ndx = x * width + y;
	    Pixel p = col ? col[c[ndx]] : c[ndx];
	    XSetForeground(display, dagc, p);
	    XDrawPoint(display, pmap, dagc, x, y);
	}
#if defined(TEST)
    static int numframe = 0;
    ostrstream(buf,STRLEN) << "Image " << (numframe++) << ends;
    int slen = strlen(buf);
    int swdt = XTextWidth(font.xfs, buf, slen);
    XSetFont(display, dagc, font.xfs->fid);
    XSetForeground(display, dagc, colortbl[white_color]);
    XDrawString(display, pmap, dagc,
	(width - swdt) / 2, font.height(), buf, slen);
#endif
    expose();
}



void Image::resize_image(int wdt, int hgt)
{
    if (!dagc) return;
    if (pmap) XFreePixmap(display, pmap);
    width = wdt;
    height = hgt;
    pmap = XCreatePixmap(display, xwnd, width, height, colorpln);
    XSetForeground(display, dagc, colortbl[black_color]);
    XFillRectangle(display, pmap, dagc, 0, 0, width, height);
}



void Image::expose()
{
    if (!pmap) return;
    XCopyArea(display, pmap, xwnd, dagc, 0, 0, width, height, 0, 0);
    XFlush(display);
}



/**** ImageWnd ****/

ImageWnd::ImageWnd(XStandardColormap* stdcmap)
    : WWindow("Image Server", stdcmap ? stdcmap->colormap : 0)
{
    smap = stdcmap;
    wvec = new WWidget*[numwgt=ii_numwgt];
    if (!wvec) return;
    memset(wvec, 0, ih_numwgt * sizeof(WWidget*));
    svrflg = cltflg = False;
    csd = dsd = 0;
    aipid = dipid = 0;
    rvec = gvec = bvec = cvec = 0;

    geom = WRect(0, 0, I_Width, I_Height + 32);
    set_geom(geom);
    XtVaSetValues(xtwgt,
	XmNresizePolicy, XmRESIZE_NONE,
	XmNnoResize, True,
	XmNshadowType, XmSHADOW_OUT,
	XmNshadowThickness, 1, 0);

    Widget mainwnd = XtVaCreateManagedWidget("mainwnd",
        xmMainWindowWidgetClass, xtwgt, 0);
    Menu* menu = new Menu(mainwnd);
    menu->add_menu("File", '\0', file_pdm);
    context->set_mainmenu(menu);
    Widget form = XtVaCreateManagedWidget("form",
	xmFormWidgetClass, mainwnd,
        XmNtopAttachment, XmATTACH_FORM,
        XmNleftAttachment, XmATTACH_FORM,
        XmNbottomAttachment, XmATTACH_FORM,
        XmNrightAttachment, XmATTACH_FORM,
        XmNshadowType, XmSHADOW_OUT,
        XmNshadowThickness, 1, 0);
    XmMainWindowSetAreas(mainwnd, menu->menubar, 0, 0, 0, form);

    Image* image = new Image(form);
    XtVaSetValues(image->xtwgt,
	XmNleftAttachment, XmATTACH_FORM,
	XmNtopAttachment, XmATTACH_FORM,
	XmNrightAttachment, XmATTACH_FORM,
	XmNbottomAttachment, XmATTACH_FORM,
	XmNbackground, colortbl[black_color], 0);
    add_widget(image, ii_image_da);

    long d = MWM_DECOR_RESIZEH | MWM_DECOR_MAXIMIZE;
    set_decoration(d, False);
    long f = MWM_FUNC_RESIZE | MWM_FUNC_MAXIMIZE;
    set_function(f, False);
    set_cp_close(cp_activate_imagewnd, 0);
}



int ImageWnd::conn_client()
{
    if (!svrflg || cltflg) return -1;

    // create data socket
    struct sockaddr_in cltaddr;
    int cltaddrlen = sizeof(cltaddr);
    dsd = accept(csd, (struct sockaddr*)&cltaddr, &cltaddrlen);
    if (dsd != -1) {
	cltflg = True;
/****
	if (fcntl(dsd, F_SETFL, O_NONBLOCK) == -1)
	    error_dialog("Cannot set non-blocking on data socket.");
****/
	// set input watcher on data socket
	aipid = XtAppAddInput(context->xtctx, dsd,
	    XtPointer(XtInputReadMask), ip_dispatch_imagewnd, 0);
    }

    // write status message
    Image* image = (Image*) wvec[ii_image_da];
    int y = I_Height * 3 / 4;
    image->clear_text();
    if (cltflg) {
	image->draw_text(y, "Client connected");
	y += 13;
	ostrstream(buf,STRLEN) << "on port " << svrport << '.' << ends;
	image->draw_text(y, buf);
    }

    return cltflg ? 0 : -1;
}



int ImageWnd::disconn_client()
{
    if (aipid) { XtRemoveInput(aipid); aipid = 0; }
    if (dipid) { XtRemoveInput(dipid); dipid = 0; }
    if (dsd && shutdown(dsd, 2) == -1) {
	error_dialog("Cannot shutdown data socket.");
	return -1;
    }
    if (dsd && close(dsd) == -1) {
	error_dialog("Cannot close data socket.");
	return -1;
    }
    dsd = 0;
    if (csd && close(csd) == -1) {
	error_dialog("Cannot close control socket.");
	return -1;
    }
    csd = 0;
    if (rvec) { delete[] rvec; rvec = 0; }
    if (gvec) { delete[] gvec; gvec = 0; }
    if (bvec) { delete[] bvec; bvec = 0; }
    if (cvec) { delete[] cvec; cvec = 0; }
    return 0;
}



int ImageWnd::dispatch()
{
    // read request type
    int net;
    if (readn(dsd, &net, sizeof(net)) == -1) {
	error_dialog("Cannot read request type.");
	return -1;
    }
    int reqtype = ntohl(net);

    // process request
    switch (reqtype) {
    case I_Disconn:
	disconn_client();
	exit(0);
	break;
    case I_ImgDim:
	resize_image();
	break;
    case I_ColRGB:
	update_colrgb();
	break;
    case I_ColMap:
	update_colmap();
	break;
    }
    return 0;
}



int ImageWnd::resize_image()
{
    // read image dimensions
    int net;
    if (readn(dsd, &net, sizeof(net)) == -1) {
	error_dialog("Cannot read image width.");
	return -1;
    }
    int wdt = ntohl(net);
    if (readn(dsd, &net, sizeof(net)) == -1) {
	error_dialog("Cannot read image height.");
	return -1;
    }
    int hgt = ntohl(net);
    geom = get_geom();
    geom.w = wdt;
    geom.h = hgt + 32;
    set_geom(geom);
    Image* image = (Image*) wvec[ii_image_da];
    image->resize_image(wdt, hgt);
    return 0;
}



void ImageWnd::decomp_colrgb()
{
/**** decompress code for RGB-based image ****/
}



int ImageWnd::update_colrgb()
{
    // read red data
    int net;
    if (readn(dsd, &net, sizeof(net)) == -1) {
	error_dialog("Cannot read size of color vector.");
	return -1;
    }
    int size = ntohl(net);
    if (!rvec) {
	rvec = new u_char[size];
	if (!rvec) {
	    error_dialog("Cannot allocate red color vector.");
	    return -1;
	}
    }
    if (readn(dsd, rvec, size * sizeof(u_char)) == -1) {
	error_dialog("Cannot read red color vector.");
	return -1;
    }

    // read green data
    if (!gvec) {
	gvec = new u_char[size];
	if (!gvec) {
	    error_dialog("Cannot allocate green color vector.");
	    return -1;
	}
    }
    if (readn(dsd, gvec, size * sizeof(u_char)) == -1) {
	error_dialog("Cannot read green color vector.");
	return -1;
    }

    // read blue data
    if (!bvec) {
	bvec = new u_char[size];
	if (!bvec) {
	    error_dialog("Cannot allocate blue color vector.");
	    return -1;
	}
    }
    if (readn(dsd, bvec, size * sizeof(u_char)) == -1) {
	error_dialog("Cannot read blue color vector.");
	return -1;
    }

    Image* image = (Image*) wvec[ii_image_da];
    image->draw_image(rvec, gvec, bvec);
    return 0;
}



void ImageWnd::decomp_colmap()
{
/**** decompress code for color map-based image ****/
}



int ImageWnd::update_colmap()
{
    // read cvec[]
    int net;
    if (readn(dsd, &net, sizeof(net)) == -1) {
	error_dialog("Cannot read size of colormap index vector.");
	return -1;
    }
    int size = ntohl(net);
    if (!cvec) {
	cvec = new u_char[size];
	if (!cvec) {
	    error_dialog("Cannot allocate cvec[] colormap index vector.");
	    return -1;
	}
    }
    if (readn(dsd, cvec, size * sizeof(u_char)) == -1) {
	error_dialog("Cannot read cvec[] colormap index vector.");
	return -1;
    }

    Image* image = (Image*) wvec[ii_image_da];
    image->draw_image(cvec);
    return 0;
}



int ImageWnd::start_server(int port)
{
    // get server host entry
    char hname[STRLEN];
    gethostname(hname, STRLEN);
    struct hostent* hent = gethostbyname(hname);
    if (!hent) {
	ostrstream(buf,STRLEN) << "Cannot get host entry for "
	    << hname << '.' << ends;
	error_dialog(buf);
	return -1;
    }

    // create and listen on control socket
    csd = socket(AF_INET, SOCK_STREAM, 0);
    if (!csd) {
	error_dialog("Cannot create control socket.");
	return -1;
    }
/****
    if (fcntl(csd, F_SETFL, O_NONBLOCK) == -1) {
        error_dialog("Cannot set non-blocking on control socket.");
	return -1;
    }
****/
    // set input watcher on data socket
    dipid = XtAppAddInput(context->xtctx, csd,
	XtPointer(XtInputReadMask), ip_accept_imagewnd, 0);
    struct sockaddr_in svraddr;
    memset(&svraddr, 0, sizeof(svraddr));
    svraddr.sin_family = AF_INET;
    svraddr.sin_port = htons(svrport = port);
    svraddr.sin_addr.s_addr = ((struct in_addr*)(hent->h_addr))->s_addr;
    if (bind(csd, (struct sockaddr*)&svraddr, sizeof(svraddr)) == -1) {
        error_dialog("Cannot bind control socket.");
	return -1;
    }
    if (listen(csd, 5) == -1) {
	error_dialog("Cannot listen on control socket.");
	return -1;
    }
    svrflg = True;

    // write status message
    Image* image = (Image*) wvec[ii_image_da];
    int y = I_Height * 3 / 4;
    image->clear_text();
    image->draw_text(y, "Waiting for");
    y += 13;
    image->draw_text(y, "client connection");
    y += 13;
    ostrstream(buf,STRLEN) << "on port " << svrport << '.' << ends;
    image->draw_text(y, buf);

    return 0;
}
