/*
 * Functions involving the info/identify window.
 */

#include "ctwm.h"

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

#include <X11/Xatom.h>

#include "ctopts.h"
#include "drawing.h"
#include "functions.h"
#include "functions_internal.h"
#include "icons.h"
#include "otp.h"
#include "r_area.h"
#include "r_layout.h"
#include "screen.h"
#include "version.h"
#include "vscreen.h"


/* We hold it in a big static buffer */
#define INFO_LINES 30
#define INFO_SIZE 200
static char Info[INFO_LINES][INFO_SIZE];

/* The builder */
static void Identify(TwmWindow *t);


/*
 * The functions that cause this to pop up.
 *
 * n.b.: these are referenced in the Developer Manual in doc/devman/; if
 * you make any changes here be sure to tweak that if necessary.
 */
DFHANDLER(identify)
{
	Identify(tmp_win);
}

DFHANDLER(version)
{
	Identify(NULL);
}


/*
 * Building and displaying.
 */

/*
 * Backend for f.identify and f.version: Fills in the Info array with the
 * appropriate bits for ctwm and the window specified (if any), and
 * sizes/pops up the InfoWindow.
 *
 * Notably, the bits of Info aren't written into the window during this
 * process; that happens later as a result of the expose event.
 */
static void
Identify(TwmWindow *t)
{
	int i, n, twidth, width, height;
	int x, y;
	unsigned int wwidth, wheight, bw, depth;
	Window junk;
	int px, py, dummy;
	unsigned udummy;
	unsigned char *prop;
	unsigned long nitems, bytesafter;
	Atom actual_type;
	int actual_format;
	XRectangle inc_rect;
	XRectangle logical_rect;
	char *ctopts;

	/*
	 * Include some checking we don't blow out _LINES.  We use snprintf()
	 * exclusively to avoid blowing out _SIZE.
	 *
	 * In an ideal world, we'd probably fix this to be more dynamically
	 * allocated, but this will do for now.
	 */
	n = 0;
#define CHKN do { \
                if(n > (INFO_LINES - 3)) { \
                        fprintf(stderr, "Overflowing Info[] on line %d\n", n); \
                        sprintf(Info[n++], "(overflow)"); \
                        goto info_dismiss; \
                } \
        } while(0)

	snprintf(Info[n++], INFO_SIZE, "Twm version:  %s", TwmVersion);
	CHKN;
	if(VCSRevision) {
		snprintf(Info[n++], INFO_SIZE, "VCS Revision:  %s", VCSRevision);
		CHKN;
	}

	ctopts = ctopts_string(", ");
	snprintf(Info[n++], INFO_SIZE, "Compile time options : %s", ctopts);
	free(ctopts);
	CHKN;

	Info[n++][0] = '\0';
	CHKN;

	if(t) {
		// The border would be on the frame, not t->w, so assume our
		// internal tracking is right for it
		XGetGeometry(dpy, t->w, &JunkRoot, &JunkX, &JunkY,
		             &wwidth, &wheight, &JunkBW, &depth);
		bw = t->frame_bw;
		XTranslateCoordinates(dpy, t->w, Scr->Root, 0, 0,
		                      &x, &y, &junk);
		snprintf(Info[n++], INFO_SIZE, "Name               = \"%s\"",
		         t->name);
		CHKN;
		snprintf(Info[n++], INFO_SIZE, "Class.res_name     = \"%s\"",
		         t->class.res_name);
		CHKN;
		snprintf(Info[n++], INFO_SIZE, "Class.res_class    = \"%s\"",
		         t->class.res_class);
		CHKN;
		Info[n++][0] = '\0';
		CHKN;
		snprintf(Info[n++], INFO_SIZE,
		         "Geometry/root (UL) = %dx%d+%d+%d (Inner: %dx%d+%d+%d)",
		         wwidth + 2 * (bw + t->frame_bw3D),
		         wheight + 2 * (bw + t->frame_bw3D) + t->title_height,
		         x - (bw + t->frame_bw3D),
		         y - (bw + t->frame_bw3D + t->title_height),
		         wwidth, wheight, x, y);
		CHKN;
		snprintf(Info[n++], INFO_SIZE,
		         "Geometry/root (LR) = %dx%d-%d-%d (Inner: %dx%d-%d-%d)",
		         wwidth + 2 * (bw + t->frame_bw3D),
		         wheight + 2 * (bw + t->frame_bw3D) + t->title_height,
		         Scr->rootw - (x + wwidth + bw + t->frame_bw3D),
		         Scr->rooth - (y + wheight + bw + t->frame_bw3D),
		         wwidth, wheight,
		         Scr->rootw - (x + wwidth), Scr->rooth - (y + wheight));
		CHKN;
		snprintf(Info[n++], INFO_SIZE, "Border width       = %d", bw);
		CHKN;
		snprintf(Info[n++], INFO_SIZE, "3D border width    = %d", t->frame_bw3D);
		CHKN;
		snprintf(Info[n++], INFO_SIZE, "Depth              = %d", depth);
		CHKN;
		if(t->vs &&
		                t->vs->wsw &&
		                t->vs->wsw->currentwspc) {
			snprintf(Info[n++], INFO_SIZE, "Virtual Workspace  = %s",
			         t->vs->wsw->currentwspc->name);
			CHKN;
		}
		snprintf(Info[n++], INFO_SIZE, "OnTopPriority      = %d",
		         OtpEffectiveDisplayPriority(t));
		CHKN;

		if(t->icon != NULL) {
			int iwx, iwy;

			XGetGeometry(dpy, t->icon->w, &JunkRoot, &iwx, &iwy,
			             &wwidth, &wheight, &bw, &depth);
			Info[n++][0] = '\0';
			CHKN;
			snprintf(Info[n++], INFO_SIZE, "IconGeom/root     = %dx%d+%d+%d",
			         wwidth, wheight, iwx, iwy);
			CHKN;
			snprintf(Info[n++], INFO_SIZE, "IconGeom/intern   = %dx%d+%d+%d",
			         t->icon->w_width, t->icon->w_height,
			         t->icon->w_x, t->icon->w_y);
			CHKN;
			snprintf(Info[n++], INFO_SIZE, "IconBorder width  = %d", bw);
			CHKN;
			snprintf(Info[n++], INFO_SIZE, "IconDepth         = %d", depth);
			CHKN;
		}

		if(XGetWindowProperty(dpy, t->w, XA_WM_CLIENT_MACHINE, 0L, 64, False,
		                      XA_STRING, &actual_type, &actual_format, &nitems,
		                      &bytesafter, &prop) == Success) {
			if(nitems && prop) {
				snprintf(Info[n++], INFO_SIZE, "Client machine     = %s",
				         (char *)prop);
				XFree(prop);
				CHKN;
			}
		}
		Info[n++][0] = '\0';
		CHKN;
	}

#undef CHKN
info_dismiss:
	snprintf(Info[n++], INFO_SIZE, "Click to dismiss....");


	/*
	 * OK, it's all built now.
	 */

	/* figure out the width and height of the info window */
	height = n * (Scr->DefaultFont.height + 2);
	width = 1;
	for(i = 0; i < n; i++) {
		XmbTextExtents(Scr->DefaultFont.font_set, Info[i],
		               strlen(Info[i]), &inc_rect, &logical_rect);

		twidth = logical_rect.width;
		if(twidth > width) {
			width = twidth;
		}
	}

	/* Unmap if it's currently up, while we muck with it */
	if(Scr->InfoWindow.mapped) {
		XUnmapWindow(dpy, Scr->InfoWindow.win);
		/* Don't really need to bother since we're about to reset, but...  */
		Scr->InfoWindow.mapped = false;
	}

	/* Stash the new number of lines */
	Scr->InfoWindow.lines = n;

	width += 10;                /* some padding */
	height += 10;               /* some padding */
	if(XQueryPointer(dpy, Scr->Root, &JunkRoot, &JunkChild,
	                 &dummy, &dummy, &px, &py, &udummy)) {
		px -= width / 2;
		py -= height / 3;
	}
	else {
		px = py = 0;
	}

	{
		RArea area = RAreaNew(px, py, width, height);
		int min_x, min_y, max_bottom, max_right;

		RLayoutFindLeftRightEdges(Scr->Layout, &area, &min_x, &max_right);
		if(px < min_x) {
			px = min_x;
		}
		else if(px + width - 1 > max_right) {
			px = max_right - width + 1;
		}

		RLayoutFindTopBottomEdges(Scr->Layout, &area, &min_y, &max_bottom);
		if(py < min_y) {
			py = min_y;
		}
		else if(py + height - 1 > max_bottom) {
			py = max_bottom - height + 1;
		}
	}
	XMoveResizeWindow(dpy, Scr->InfoWindow.win, px, py, width, height);
	XMapRaised(dpy, Scr->InfoWindow.win);
	Scr->InfoWindow.mapped = true;
	Scr->InfoWindow.width  = width;
	Scr->InfoWindow.height = height;
}


/*
 * And the routine to actually write the text into the InfoWindow.  This
 * gets called from events.c as a result of Expose events on the window.
 */
void
draw_info_window(void)
{
	int i;
	const int height = Scr->DefaultFont.height + 2;

	Draw3DBorder(Scr->InfoWindow.win, 0, 0,
	             Scr->InfoWindow.width, Scr->InfoWindow.height,
	             2, Scr->DefaultC, off, true, false);

	FB(Scr->DefaultC.fore, Scr->DefaultC.back);

	for(i = 0; i < Scr->InfoWindow.lines ; i++) {
		XmbDrawString(dpy, Scr->InfoWindow.win, Scr->DefaultFont.font_set,
		              Scr->NormalGC, 5,
		              (i * height) + Scr->DefaultFont.y + 5,
		              Info[i], strlen(Info[i]));
	}
}
