/*
 * Copyright (c) 1991-1993 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Header: sitebox.cc,v 1.27 96/05/16 05:21:35 van Exp $ (LBL)";
#endif

#ifndef WIN32
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#if defined(sgi)
#define vfork fork
#endif

#include "Tcl.h"
#include "tkwidget.h"
#include "idlecallback.h"

#include <string.h>

class SiteBox;

class Site : public TclObject {
    public:
	Site(Tk_Window, SiteBox&);
	~Site();

	void draw() const;
	virtual int command(int argc, const char*const* argv);

	int height();
	void place(int x, int y, int w, int h);

	static int textwidth(const char* s);
	inline const char* text() const { return (text_); }
	inline const char* tag() const { return (tag_); }

	Site* next_;
    protected:
	void text(const char*);
	void tag(const char*);
	void draw_checkbox(Display*, Drawable, GC) const;
	void square(Display* dpy, Drawable window, GC gc,
		    int x, int y, int side) const;
	GC raw_gc(Font fid, XColor* fg, XColor* bg, Drawable d) const;
	void free_gc(GC gc) const;

	char* text_;		/* string appearing in display */
	char* tag_;		/* string appearing in display */
	int textlen_;		/* strlen(text_) */
	int text_x_;
	int text_y_;
	int x_;
	int y_;
	int height_;
	int width_;
	int descent_;
	int ascent_;
	int minor_;
	int major_;

#define SF_HIGHLIGHT	0x01
#define SF_DISABLE	0x02
	int state_;
	int mute_;
	int rank_;
	Tk_Window tk_;
	SiteBox& sitebox_;

	static GC copy_gc_;
	static GC fg_[4];
	static GC bg_[4];
	static XColor* fc_;	/* foreground */
	static XColor* bc_;	/* background */
	static XColor* ac_;	/* activeBackground */
	static XColor* dc_;	/* disabled */
	static Drawable pixmap_;
	static int pixw_;
	static int pixh_;
	static XFontStruct* fs_;	/* font metrics */
};

class SiteBox : public TkWidget, public IdleCallback {
    public:
	SiteBox(const char* path);
	virtual int command(int argc, const char*const* argv);
	void setchan(int channel);
	void LowLightAll();
	inline int Nsites() const { return (nsites_); }
	void idle_callback();
	void change_name();
    private:
	Site* create();
	Site* remove(const char* n);
	void reset();
	void append(Site* s);
	void layout();
	void sort_sites();
	virtual void resize();
	virtual void draw();
	virtual void update();

	Site** sitelist_;
	int nsites_;
	int nsitelist_;
	int empties_;

	int ncol_;		/* number of columns in site display */
	int colitems_;		/* number of items that fit for ncol_ */
	int percol_;		/* number of sites per column */
	int itemht_;		/* vertical dist allocated to a site */

	int place_x_;
	int place_y_;

	GC bg_;
	GC fg_;

	int sorted_;
	int need_sort_;
	int need_layout_;
	int need_redraw_;
};

GC Site::copy_gc_;
GC Site::fg_[4];
GC Site::bg_[4];
XFontStruct* Site::fs_;
XColor* Site::fc_;
XColor* Site::bc_;
XColor* Site::ac_;
XColor* Site::dc_;

Drawable Site::pixmap_;
int Site::pixw_;
int Site::pixh_;

const int vpad = 2;

Site::Site(Tk_Window tk, SiteBox& sb) :
	next_(0),
	text_(0),
	tag_(0),
	textlen_(0),
	text_x_(0),
	text_y_(0),
	x_(0),
	y_(0),
	height_(0),
	width_(0),
	state_(0),
	mute_(0),
	rank_(4),
	tk_(tk),
	sitebox_(sb)
{
	Tcl& tcl = Tcl::instance();
	if (fs_ == 0) {
		const char* font = tcl.attr("siteFont");
		fs_ = Tk_GetFontStruct(tcl.interp(), tk_, (char*)font);
		if (fs_ == 0) {
			fprintf(stderr,
				"vat: couldn't find font: %s\n", font);
			fs_ = Tk_GetFontStruct(tcl.interp(), tk_, "screen");
			if (fs_ == 0)
				fs_ = Tk_GetFontStruct(tcl.interp(), tk_,
						       "fixed");
			if (fs_ == 0) {
				fprintf(stderr,
				  "vat: couldn't find screen or fixed font\n");
				exit(1);
			}
		}
		if (sitebox_.mono()) {
			fc_ = sitebox_.getcolor("black", "black");
			bc_ = sitebox_.getcolor("white", "white");
		} else {
			fc_ = sitebox_.getcolor(tcl.attr("foreground"),
						"black");
			bc_ = sitebox_.getcolor(tcl.attr("background"),
						"white");
		}
		ac_ = sitebox_.getcolor(tcl.attr("highlightColor"), "white");
		dc_ =  sitebox_.getcolor(tcl.attr("disabledColor"), "gray");

		copy_gc_ = sitebox_.lookup_gc(0, 0, 0);
	}
	descent_ = fs_->descent;
	ascent_ = fs_->ascent;
	major_ = ascent_;
	minor_ = major_ / 2;
}

Site::~Site()
{
}

int Site::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	if (argc == 2) {
		if (strcmp(argv[1], "text") == 0) {
			tcl.result(text_);
			return (TCL_OK);
		}
		if (strcmp(argv[1], "mute") == 0) {
			tcl.result(mute_ ? "1" : "0");
			return (TCL_OK);
		}
		if (strcmp(argv[1], "rank") == 0) {
			sprintf(tcl.buffer(), "%d", rank_);
			tcl.result(tcl.buffer());
			return (TCL_OK);
		}
	} else if (argc == 3) {
		if (strcmp(argv[1], "highlight") == 0) {
			if (atoi(argv[2]))
				state_ |= SF_HIGHLIGHT;
			else
				state_ &=~ SF_HIGHLIGHT;
			draw();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "disable") == 0) {
			if (atoi(argv[2]))
				state_ |= SF_DISABLE;
			else
				state_ &=~ SF_DISABLE;
			draw();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "tag") == 0) {
			tag(argv[2]);
			return (TCL_OK);
		}
		if (strcmp(argv[1], "text") == 0) {
			text(argv[2]);
			sitebox_.change_name();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "mute") == 0) {
			mute_ = atoi(argv[2]);
			draw();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "rank") == 0) {
			rank_ = atoi(argv[2]);
			draw();
			return (TCL_OK);
		}
	}
	return (TclObject::command(argc, argv));
}

/*
 * Must be called after first site created.
 */
inline int Site::textwidth(const char* s)
{
	return (XTextWidth(fs_, s, strlen(s)));
}

inline void Site::square(Display* dpy, Drawable window, GC gc,
			 int x, int y, int side) const
{
	int r = side >> 1;
	side = (r << 1) + 1;
	XFillRectangle(dpy, window, gc, x - r, y - r, side, side);
}

void Site::draw_checkbox(Display* dpy, Drawable window, GC gc) const
{
	int cy = (height_ - 1) / 2;
	int cx = 2 + cy;
	
#ifdef notdef
	cx += x_;
	cy += y_;
#endif
	/*
	 * Draw the rank indicator.
	 */
	if (rank_ < 3) {
		int side = minor_;
		switch (rank_) {
		case 0:
			square(dpy, window, gc, cx, cy, side);
			break;
		case 2:
			side -= 2;
			/* fall through */
		case 1:
			square(dpy, window,
			       (state_ >= 2) ? gc : fg_[state_ + 2],
			       cx, cy, side);
			break;
		}
	}
	/*
	 * Draw the mute button.
	 */
	const int r = major_ >> 1;
	const int s = r << 1;
	XDrawRectangle(dpy, window, gc, cx - r, cy - r, s, s);
	if (mute_) {
		/*
		 * Draw an `X' through the mute button.
		 */
		XDrawLine(dpy, window, gc, cx - r, cy + r, cx + r, cy - r);
		XDrawLine(dpy, window, gc, cx - r, cy - r, cx + r, cy + r);
	}
}

void Site::draw() const
{
	if (width_ == 0)
		return;

	Display* dpy = Tk_Display(tk_);
	Drawable d = pixmap_;
	if (d == 0)
		return;
	GC gc = fg_[state_];

	XFillRectangle(dpy, d, bg_[state_], 0, 0, width_, height_);
	draw_checkbox(dpy, d, gc);
	XDrawString(dpy, d, gc, text_x_, text_y_, text_, textlen_);
	XDrawLine(dpy, d, fg_[0], 0, height_ - 1, width_ - 1, height_ - 1);
	XCopyArea(dpy, d, Tk_WindowId(tk_), copy_gc_,
		  0, 0, width_, height_, x_, y_);
}

void Site::text(const char* newtext)
{
	delete text_;
	textlen_ = strlen(newtext);
	text_ = new char[textlen_ + 1];
	strcpy(text_, newtext);
	draw();
}

void Site::tag(const char* s)
{
	delete tag_;
	tag_ = new char[strlen(s) + 1];
	strcpy(tag_, s);
}

int Site::height()
{
	/*
	 * Make sure height allocation is even.  So less one
	 * pixel for bottom border is odd, which gives us a 
	 * center line.
	 */
	int h = ascent_ + descent_ + 2 * vpad;
	h += h & 1;
	return (h);
}

/*
 * XXX Allocate a gc without going through tk.  We cannot use
 * TkWidget::lookup_gc because it allocates the gc using the
 * on-screen window and we need to allocate it using the pixmap
 * to workaround a bug in the Parallax X server.
 */
GC Site::raw_gc(Font fid, XColor* fg, XColor* bg, Drawable d) const
{
	XGCValues v;
	u_long mask;
	sitebox_.set_gcv(v, mask, fid, fg, bg);
#ifdef PARALLAX_BUG
	return (XCreateGC(Tk_Display(tk_), d, mask, &v));
#else
	return (Tk_GetGC(tk_, mask, &v));
#endif
}

void Site::free_gc(GC gc) const
{
#ifdef PARALLAX_BUG
	XFreeGC(Tk_Display(tk_), gc);
#else
	Tk_FreeGC(Tk_Display(tk_), gc);
#endif
}

void Site::place(int x, int y, int w, int h)
{
	width_ = w;
	height_ = h;
	x_ = x;
	y_ = y;

	text_x_ = height_ + 4;
	text_y_ = height_ - (descent_ + vpad + 1);

	if ((pixw_ < w || pixh_ < h) && w > 0 && h > 0) {
		pixw_ = w;
		pixh_ = h;
		Display* dpy = Tk_Display(tk_);
		if (pixmap_ != 0)
			XFreePixmap(dpy, pixmap_);
		pixmap_ = XCreatePixmap(dpy, Tk_WindowId(tk_), w, h,
					Tk_Depth(tk_));
		/*
		 * Reallocate GCs on this pixmap to work around a bug
		 * in the Parallax X server.
		 */
		if (fg_[0] != 0) {
			free_gc(fg_[0]);
			free_gc(fg_[1]);
			free_gc(fg_[2]);
			free_gc(fg_[3]);
			free_gc(bg_[0]);
			free_gc(bg_[1]);
		}
		Font fid = fs_->fid;
		fg_[0] = raw_gc(fid, fc_, bc_, pixmap_);
		fg_[2] = raw_gc(fid, dc_, bc_, pixmap_);
		bg_[0] = raw_gc(fid, bc_, bc_, pixmap_);
		if (sitebox_.mono()) {
			fg_[1] = raw_gc(fid, bc_, fc_, pixmap_);
			fg_[3] = raw_gc(fid, dc_, fc_, pixmap_);
			bg_[1] = raw_gc(fid, fc_, fc_, pixmap_);
		} else {
			fg_[1] = raw_gc(fid, fc_, ac_, pixmap_);
			fg_[3] = raw_gc(fid, dc_, ac_, pixmap_);
			bg_[1] = raw_gc(fid, ac_, ac_, pixmap_);
		}
		bg_[2] = bg_[0];
		bg_[3] = bg_[1];
	}
}

void SiteBox::change_name()
{
	if (sorted_) {
		need_sort_ = 1;
		idle_sched();
	}
}

void SiteBox::resize()
{
	reset();
	layout();
}

void SiteBox::layout()
{
	need_layout_ = 0;
	int n = nsites_;
	if (n <= 0) {
		place_x_ = 0;
		place_y_ = 0;
		return;
	}
	percol_ = height_ / itemht_;
	if (percol_ <= 0)
		percol_ = 1;
	ncol_ = (n + percol_ - 1) / percol_;
	if (ncol_ <= 0)
		ncol_ = 1;

	colitems_ = ncol_ * percol_;
	int w =  width_ / ncol_;
	int j = 0;
	int x = -w;
	int y = 0;
	int nc = ncol_;
	for (int i = 0; i < n; i++) {
		if (--j <= 0) {
			j = percol_;
			y = 0;
			x += w;
			if (--nc <= 0)
				/*
				 * Absorb round off error in
				 * last column
				 */
				w = width_ - (ncol_ - 1) * w;
		}
		Site* s = sitelist_[i];
		if (s != 0)
			s->place(x, y, w, itemht_);

		y += itemht_;
	}
	place_x_ = x;
	place_y_ = y;
}

void SiteBox::reset()
{
	ncol_ = 1;
	colitems_ = 0;
	place_x_ = 0;
	place_y_ = 0;
}

/*
 * Place a new site in the next slot.
 * If we run out of room, do a resize
 * to force a new layout computation.
 */
void SiteBox::append(Site* s)
{
	int n = nsites_;
	if (n > colitems_) {
		if (n == 1)
			itemht_ = s->height();
		need_layout_ = 1;
	} else {
		/*
		 * Place this site below the previous one.
		 */
		int w = width_ / ncol_;
		w = width_ - (ncol_ - 1) * w;
		s->place(place_x_, place_y_, w, itemht_);
		place_y_ += itemht_;
	}
	need_redraw_ = 1;
	if (sorted_)
		need_sort_ = 1;
	idle_sched();
}

void SiteBox::update()
{
	draw();
}

/*
 * This is a heavyweight redraw.  It gets called only on exposures.
 * Highlighting etc. is generally done directly by the Site objects.
 */
void SiteBox::draw()
{
	need_redraw_ = 0;
	int n = nsites_;
	if (n <= 0 || width_ == 0)
		return;

	/*
	 * All sites get redrawn below and they fill in their
	 * whole allocation.  So to avoid screen flicker,
	 * only erase the blank areas.
	 */
	if (ncol_ > 1) {
		XFillRectangle(Tk_Display(tk_), Tk_WindowId(tk_), bg_,
			       place_x_ + 1, place_y_, 
			       width_ - place_x_, height_ - place_y_);
		int h = height_ - percol_ * itemht_;
		if (h > 0)
			XFillRectangle(Tk_Display(tk_), Tk_WindowId(tk_), bg_,
				       0, height_ - h, width_, h);
	} else {
		XFillRectangle(Tk_Display(tk_), Tk_WindowId(tk_), bg_,
			       0, place_y_, width_, height_ - place_y_);
	}

	Display* const dpy = Tk_Display(tk_);
	const Drawable window = Tk_WindowId(tk_);
	for (int i = 0; i < n; i++) {
		Site* s = sitelist_[i];
		if (s != 0)
			s->draw();
	}
	if (ncol_ > 1 && colitems_ > n) {
		int x = (ncol_ - 1) * (width_ / ncol_);
		int h = (colitems_ - n) * itemht_;
		int bot = percol_ * itemht_;
		XDrawLine(dpy, window, fg_, x, bot - h, x, bot);
	}
}

static class SiteBoxMatcher : public Matcher {
public:
	SiteBoxMatcher() : Matcher("sitebox") {}
	TclObject* match(const char* path) {
		return (new SiteBox(path));
	}
} sitebox_matcher;
	
SiteBox::SiteBox(const char* path)
	: TkWidget(path, "Vat", 0, 0, 0)
{
	nsitelist_ = 256;
	sitelist_ = (Site**)malloc(nsitelist_ * sizeof(Site*));
	memset((char*)sitelist_, 0, nsitelist_ * sizeof(Site*));
	nsites_ = 0;
	empties_ = 0;

	ncol_ = 1;
	colitems_ = 0;
	itemht_ = 1;
	percol_ = 1;

	sorted_ = 1;
	need_sort_ = 0;

	Tcl& tcl = Tcl::instance();
	Tk_Uid fg = mono()? "black" : (char*)tcl.attr("foreground");
	Tk_Uid bg = mono()? "white" : (char*)tcl.attr("background");
	fg_ = lookup_gc(0, fg, bg);
	bg_ = lookup_gc(0, bg, bg);
}

Site* SiteBox::create()
{
	Site* s = new Site(tk_, *this);
	sitelist_[nsites_] = s;
	/* make sure table is null terminated */
	if (++nsites_ >= nsitelist_) {
		int n = nsitelist_ * 2;
		sitelist_ = (Site**)realloc(sitelist_, n * sizeof(*sitelist_));
		memset((char*)&sitelist_[nsitelist_], 0,
		      nsitelist_ * sizeof(*sitelist_));
		nsitelist_ = n;
	}
	return (s);
}

Site* SiteBox::remove(const char* n)
{
	Site* s;
	for (Site **sp = sitelist_; (s = *sp) != 0; ++sp) {
		if (strcmp(s->name(), n) == 0) {
			--nsites_;
			Site* target = s;
			for (;;) {
				s = sp[1];
				if ((*sp++ = s) == 0)
					return (target);
			}
		}
	}
	return (0);
}

static int
compareSite(const void* p1, const void* p2)
{
	Site* s1 = *(Site**)p1;
	Site* s2 = *(Site**)p2;

	const char* n1 = s1->text();
	while (*n1 && isascii(*n1) && !isalpha(*n1))
		++n1;
	const char* n2 = s2->text();
	while (*n2 && isascii(*n2) && !isalpha(*n2))
		++n2;
	return (strcasecmp(n1, n2));
}

void SiteBox::sort_sites()
{
	register int n = nsites_;
	Site** sp = sitelist_;
	qsort(sp, n, sizeof(*sp),
	      (int(*)(const void*, const void*))compareSite);
	need_sort_ = 0;
}

void SiteBox::idle_callback()
{
	if (need_sort_) {
		sort_sites();
		need_layout_ = 1;
	}
	if (need_layout_) {
		reset();
		layout();
		need_redraw_ = 1;
	}
	if (need_redraw_)
		draw();
}

int SiteBox::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	register int i;
	if (argc == 2) {
		if (strcmp(argv[1], "create") == 0) {
			Site* s = create();
			append(s);
			tcl.result(s->name());
			return (TCL_OK);
		}
		if (strcmp(argv[1], "sites") == 0) {
			sprintf(tcl.buffer(), "%d", nsites_);
			tcl.result(tcl.buffer());
			return (TCL_OK);
		} 
		if (strcmp(argv[1], "sort") == 0) {
			need_sort_ = 1;
			idle_sched();
			return (TCL_OK);
		}
	} else if (argc == 3) {
		if (strcmp(argv[1], "remove") == 0) {
			Site* s = remove(argv[2]);
			delete s;
			need_layout_ = 1;
			idle_sched();
			return (TCL_OK);
		}
		if (strcmp(argv[1], "keep-sorted") == 0) {
			sorted_ = atoi(argv[2]);
			if (sorted_) {
				need_sort_ = 1;
				idle_sched();
			}
			return (TCL_OK);
		}
	} else if (argc == 4) {
		if (strcmp(argv[1], "which") == 0) {
			int x, y, h, v, dw;
			x = atoi(argv[2]);
			y = atoi(argv[3]);
			v = y / itemht_;
			dw = width_ / ncol_;
			h = x / dw;
			i = h * percol_ + v;
			if (i >= 0 && i < nsites_) {
				const char* s = sitelist_[i]->tag();
				if (s != 0)
					tcl.result(s);
			}
			return (TCL_OK);
		}
		if (strcmp(argv[1], "over-button") == 0) {
			int x, y, h, v, dw, cb = 0;
			x = atoi(argv[2]);
			y = atoi(argv[3]);
			v = y / itemht_;
			dw = width_ / ncol_;
			h = x / dw;
			i = h * percol_ + v;
			if (0 <= i && i < nsites_ && x - h * dw < itemht_)
				cb = 1;
			sprintf(tcl.buffer(), "%d", cb);
			tcl.result(tcl.buffer());
			return (TCL_OK);
		}
	}
	return (TclObject::command(argc, argv));
}
