/*
 * 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 char rcsid[] =
    "@(#) $Header: /usr/src/mash/repository/vint/ns-2/nam/netview.cc,v 1.4 1997/08/10 07:50:06 mccanne Exp $ (LBL)";
#endif

#include <stdlib.h>
#include <ctype.h>

extern "C" {
#include <tk.h>
}

#include "bbox.h"
#include "netview.h"
#include "netmodel.h"
#include "tclcl.h"
#include "paint.h"
#include "nam-packet.h"

void NetView::resize(int width, int height)
{
	width_ = width;
	height_ = height;

	matrix_.clear();

	BBox bb;
	model_->BoundingBox(bb);
	
	double x = 0;
	double y = 0;
	double w = width;
	double h = height;
	/*
	 * Set up a transform that maps bb -> canvas.  I.e,
	 * bb -> unit square -> allocation, but which retains
	 * the aspect ratio.  Also, add a margin.
	 */
	double nw = bb.xmax - bb.xmin;
	double nh = bb.ymax - bb.ymin;
	
	/* 
	 * Grow a margin.
	 */
	double bbw = 1.1 * nw;
	double bbh = 1.1 * nh;
	double tx = bb.xmin - 0.5 * (bbw - nw);
	double ty = bb.ymin - 0.5 * (bbh - nh);
	
	/*
	 * move base coordinate system to origin
	 */
	matrix_.translate(-tx, -ty);
	/*
	 * flip vertical axis because X is backwards.
	 */
	matrix_.scale(1., -1.);
	matrix_.translate(0., bbh);
	
	double ws = w / bbw;
	double hs = h / bbh;
	
	if (ws <= hs) {
		matrix_.scale(ws, ws);
		matrix_.translate(x, y + 0.5 * (h - ws * bbh));
	} else {
		matrix_.scale(hs, hs);
		matrix_.translate(x + 0.5 * (w - hs * bbw), y);
	}
	XWindowAttributes xwa;
	XGetWindowAttributes(dpy_, drawable_, &xwa);
	offscreen_ = XCreatePixmap(dpy_, drawable_, width_, height_, xwa.depth);
}

void NetView::draw()
{
	if (offscreen_ == 0)
		return;
	
	XFillRectangle(dpy_, offscreen_, background_, 0, 0, width_, height_);
	model_->render(this);
	XCopyArea(dpy_, offscreen_, drawable_, background_,
		  0, 0, width_, height_, 0, 0);
}

NetView::NetView(const char* name, NetModel* m) 
        : model_(m)
{
	Tcl& tcl = Tcl::instance();
	tk_ = Tk_CreateWindowFromPath(tcl.interp(), tcl.tkmain(),
				      (char*)name, 0);
	if (tk_ == 0)
		abort();
	
	Tk_SetClass(tk_, "NetView");
	
	/*XXX*/
	/* Specify preferred window size. */
	Tk_GeometryRequest(tk_, 50, 50);

	Tk_CreateEventHandler(tk_, ExposureMask|StructureNotifyMask,
			      handle, (ClientData)this);

	width_ = height_ = 0;

	tcl.CreateCommand(Tk_PathName(tk_), command, (ClientData)this, 0);
    
	dpy_ = Tk_Display(tk_);
	background_ = Paint::instance()->background_gc();

	Tk_MakeWindowExist(tk_);
    
	drawable_ = Tk_WindowId(tk_);
	offscreen_ = 0;
    
	load_fonts();
}

/* Handler for the Expose, DestroyNotify and ConfigureNotify events. */
void NetView::handle(ClientData cd, XEvent* ep)
{
	NetView* nv = (NetView*)cd;
	
	switch (ep->type) {
	case Expose:
		if (ep->xexpose.count == 0)
			/*XXX*/
			nv->draw();
		break;
		
	case DestroyNotify:
		/*XXX kill ourself */
		break;
		
	case ConfigureNotify:
		if (nv->width_ != ep->xconfigure.width ||
		    nv->height_ != ep->xconfigure.height)
			nv->resize(ep->xconfigure.width,
				   ep->xconfigure.height);
		break;
	}
}

extern void Parse(NetModel*, const char* layout);

int NetView::command(ClientData cd, Tcl_Interp* tcl, int argc, char **argv)
{
	if (argc < 2) {
		Tcl_AppendResult(tcl, "\"", argv[0], "\": arg mismatch", 0);
		return (TCL_ERROR);
	}
#ifdef notdef
	/*
	 * $w chart -v variable -s src -d dst -w width -h height
	 */
	NetView* nv = (NetView*)cd;
	if (strcmp(argv[1], "layout") == 0) {
		/*   <window> layout <net> */
		if (argc != 3) {
			Tcl_AppendResult(tcl, "\"", argv[0],
					 "\": arg mismatch", 0);
			return (TCL_ERROR);
		}
		Parse(nv->model_, argv[2]);
		return (TCL_OK);
	}
	if (strcmp(argv[1], "clock") == 0) {
		/*   <window> clock <proc> <interval> */
		if (argc != 4) {
			Tcl_AppendResult(tcl, "\"", argv[0],
					 "\": arg mismatch", 0);
			return (TCL_ERROR);		}
		const char* proc = argv[2];
		double interval = atof(argv[3]);
		master->clock(proc, interval);
		return (TCL_OK);
	}
	/*
	 * $nv trace callback var
	 * $nv trace callback etype src dst tag
	 */
	if (strcmp(argv[1], "trace") == 0) {
		if (argc == 4)
			nv->trace(argv[2], argv[3]);
		else if (argc == 7)
			nv->trace(argv[2], argv[3], argv[4], argv[5], argv[6]);
		else {
			Tcl_AppendResult(tcl, "\"", argv[0],
					 "\": arg mismatch", 0);
			return (TCL_ERROR);
		}
		return (TCL_OK);
	}
#endif
	if (strcmp(argv[1], "info") == 0) {
		if (argc == 3) {
			NetView *nv = (NetView *)cd;
			Tcl& tcl = Tcl::instance();
			Animation* a;
			double now = atof(argv[2]);
			Window root, child;
			int rootX, rootY, winX, winY;
			unsigned int state;
			float px, py;
 			XQueryPointer(nv->dpy_, Tk_WindowId(nv->tk_), &root,
 				      &child, &rootX, &rootY, &winX, &winY,
 				      &state);
 			nv->matrix_.imap(float(winX), float(winY), px, py);
			if ((a = nv->model_->inside(now, px, py)) != 0) {
				tcl.result(a->info());
				return TCL_OK;
			}
			return TCL_OK;
		}
		Tcl_AppendResult(tcl, "\"", argv[0],
				 "\": arg mismatch", 0);
		return TCL_ERROR;
	}
	Tcl_AppendResult(tcl, "\"", argv[0], "\": unknown arg: ", argv[1], 0);
	return (TCL_ERROR);
}

#ifdef notdef
void 
NetView::trace(const char* cmd, const char* varname)
{
	master->addFilter(new VarFilter(tcl_, cmd, varname));
}

void 
NetView::trace(const char* cmd, const char* etype,
	       const char* src, const char* dst, const char* tag)
{
	master->addFilter(new GenericFilter(tcl_, cmd, etype, src, dst, tag));
}
#endif

void 
NetView::line(float x0, float y0, float x1, float y1, int paint)
{
	int ax, ay;
	matrix_.map(x0, y0, ax, ay);
	int bx, by;
	matrix_.map(x1, y1, bx, by);
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawLine(dpy_, offscreen_, gc, ax, ay, bx, by);
}

void NetView::rect(float x0, float y0, float x1, float y1, int paint)
{
	int x, y;
	matrix_.map(x0, y0, x, y);
	int xx, yy;
	matrix_.map(x1, y1, xx, yy);
	
	int w = xx - x;
	if (w < 0) {
		x = xx;
		w = -w;
	}
	int h = yy - y;
	if (h < 0) {
		h = -h;
		y = yy;
	}
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawRectangle(dpy_, offscreen_, gc, x, y, w, h);
}

void NetView::polygon(const float* x, const float* y, int n, int paint)
{
	/*XXX*/
	XPoint pts[10];
	
	for (int i = 0; i < n; ++i) {
		float tx, ty;
		matrix_.map(x[i], y[i], tx, ty);
		pts[i].x = int(tx);
		pts[i].y = int(ty);
	}
	pts[n] = pts[0];
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawLines(dpy_, offscreen_, gc, pts, n + 1, CoordModeOrigin);
}

void NetView::fill(const float* x, const float* y, int n, int paint)
{
	/*XXX*/
	XPoint pts[10];
	
	for (int i = 0; i < n; ++i) {
		float tx, ty;
		matrix_.map(x[i], y[i], tx, ty);
		pts[i].x = int(tx);
		pts[i].y = int(ty);
	}
	pts[n] = pts[0];
	GC gc = Paint::instance()->paint_to_gc(paint);
	XFillPolygon(dpy_, offscreen_, gc, pts, n + 1,
		     Convex, CoordModeOrigin);
}

void NetView::circle(float x, float y, float r, int paint)
{
	int tx, ty;
	matrix_.map(x, y, tx, ty);
	int tr, dummy;
	matrix_.map(x + r, y, tr, dummy);
	tr -= tx;
	tx -= tr;
	ty -= tr;
	tr *= 2;
	GC gc = Paint::instance()->paint_to_gc(paint);
	XDrawArc(dpy_, offscreen_, gc, tx, ty, tr, tr, 0, 64 * 360);
}

static char* fontName[NFONT] = {
	"-adobe-times-medium-r-normal-*-8-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-10-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-12-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-14-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-18-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-20-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-24-*-*-*-*-*-*-*",
	"-adobe-times-medium-r-normal-*-34-*-*-*-*-*-*-*",
};
static Font* font[sizeof(fontName)/sizeof(fontName[0])];

/* Set the font structures using values in 'fontName' (defined above). */
void NetView::load_fonts()
{
	Tcl_Interp* tcl = Tcl::instance().interp();
	nfont_ = 0;
	for (int i = 0; i < NFONT; ++i) {
		fonts_[nfont_] = Tk_GetFontStruct(tcl, tk_, fontName[i]);
		if (fonts_[nfont_] == 0)
			continue;
		font_gc_[nfont_] =
			Paint::instance()->text_gc(fonts_[nfont_]->fid);
		++nfont_;
	}
	if (nfont_ == 0)
		fprintf(stderr, "nam: warning no fonts found\n");
}

void NetView::free_fonts()
{
	/*XXX Tk_FreeFontStruct*/
}

int NetView::lookup_font(int d)
{
	int i = nfont_;
	while (--i > 0) {
		XFontStruct* p = fonts_[i];
		if (d >= p->ascent + p->descent)
			return (i);
	}
	return (0);
}

void NetView::string(float fx, float fy, float dim, const char* s, int anchor)
{
	if (nfont_ <= 0)
		return;
	
	int dummy;
	int dlow, dhigh;
	
	/*XXX this could be cached*/
	matrix_.map(0., 0., dummy, dlow);
	matrix_.map(0., 0.9 * dim, dummy, dhigh);
	int d = dhigh - dlow;
	if (d < 0)
		d = -d;
	int font = lookup_font(d);
	XFontStruct* p = fonts_[font];
	
	/*int h = p->ascent + p->descent;*/
	int h = p->ascent;
	int len = strlen(s);
	int w = XTextWidth(p, s, len);
	
	int x, y;
	matrix_.map(fx, fy, x, y);
	
	/* XXX still need to adjust for mismatch between d and actual height*/
	
	switch (anchor) {
		
	case ANCHOR_CENTER:
		x -= w / 2;
		y += h / 2;
		break;
		
	case ANCHOR_NORTH:
		x -= w / 2;
		y -= d;
		break;
		
	case ANCHOR_SOUTH:
		x -= w / 2;
		y += d + h;
		break;
		
	case ANCHOR_WEST:
		x -= d + w;
		y += h / 2;
		break;
		
	case ANCHOR_EAST:
		x += d;
		y += h / 2;
		break;
	}
	XDrawString(dpy_, offscreen_, font_gc_[font], x, y, s, len);
}
