/*
 * Copyright (c) 1997 by the University of Southern California
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation in source and binary forms for non-commercial purposes
 * and without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both the copyright notice and
 * this permission notice appear in supporting documentation. and that
 * any documentation, advertising materials, and other materials related
 * to such distribution and use acknowledge that the software was
 * developed by the University of Southern California, Information
 * Sciences Institute.  The name of the University may not be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THE UNIVERSITY OF SOUTHERN CALIFORNIA makes no representations about
 * the suitability of this software for any purpose.  THIS SOFTWARE IS
 * PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Other copyrights might apply to parts of this software and are so
 * noted when applicable.
 *
 * $Header: /usr/src/mash/repository/vint/nam-1/editview.cc,v 1.4 1998/12/19 02:02:45 yaxu Exp $
 */

#include <stdlib.h>
#ifdef WIN32
#include <windows.h>
#endif

#include <ctype.h>
#include <math.h>

#include "view.h"
#include "bbox.h"
#include "netmodel.h"
#include "editview.h"
#include "tclcl.h"
#include "paint.h"
#include "tag.h"

void EditView::DeleteCmdProc(ClientData cd)
{
	EditView *ev = (EditView *)cd;
	if (ev->tk_ != NULL) {
		Tk_DestroyWindow(ev->tk_);
	}
}

EditView::EditView(const char *name, NetModel* m) 
        : View(name, SQUARE, 300, 700), model_(m)
{
	if (tk_!=0) {
		Tcl& tcl = Tcl::instance();
		cmd_ = Tcl_CreateCommand(tcl.interp(), Tk_PathName(tk_), 
					 command, 
					 (ClientData)this, DeleteCmdProc);
	}
	char str[256];
	sprintf(str, "def%-u", (int)this);
	defTag_ = new Tag(str);
	model_->add_tag(defTag_);
	objType_ = NONE;
}

EditView::EditView(const char *name, NetModel *m, int width, int height)
	: View(name, SQUARE, width, height), model_(m), objType_(NONE)
{
	if (tk_!=0) {
		Tcl& tcl = Tcl::instance();
		cmd_ = Tcl_CreateCommand(tcl.interp(), Tk_PathName(tk_), 
					 command, 
					 (ClientData)this, DeleteCmdProc);
	}
	defTag_ = new Tag(name);
	model_->add_tag(defTag_);
	objType_ = NONE;
}

EditView::~EditView()
{
	model_->remove_view(this);
	if (defTag_ != NULL) {
		model_->delete_tag(defTag_->name());
		delete defTag_;
		defTag_ = NULL;
	}
	// Delete Tcl command created
	Tcl& tcl = Tcl::instance();
	Tcl_DeleteCommandFromToken(tcl.interp(), cmd_);
}

int EditView::command(ClientData cd, Tcl_Interp* tcl, int argc, char **argv)
{
	if (argc < 2) {
		Tcl_AppendResult(tcl, "\"", argv[0], "\": arg mismatch", 0);
		return (TCL_ERROR);
	}

	char c = argv[1][0];
	int length = strlen(argv[1]);
	EditView *ev = (EditView *)cd;
	float cx, cy;
	int flag;

	// Following implements several useful interactive commands of 
	// Tk's canvas. Their syntax and semantics are kept as close to
	// those of Tk canvas as possible.

	if ((c == 'c') && (strncmp(argv[1], "close", length) == 0)) {
		ev->destroy();
		return TCL_OK;

	} else if ((c == 'd') && (strncmp(argv[1], "dctag", length) == 0)) {
		/*
		 * <view> dctag
		 * 
		 * Delete the current selection, start all over.
		 */
		if (ev->defTag_ != NULL)
			ev->defTag_->remove();
		ev->draw();
		return (TCL_OK);

	} else if ((c == 's') && (strncmp(argv[1], "setPoint", length) == 0)) {
		/*
		 * <view> setPoint x y addFlag
		 * 
		 * Select the object (if any) under point (x, y). If no such
		 * object, set current point to (x, y)
		 */
		if (argc != 5) {
			Tcl_AppendResult(tcl, "wrong # args: should be \"",
					 argv[0],
					 " setPoint x y addFlag\"", 
					 (char *)NULL);
			return (TCL_ERROR);
		}
		cx = strtod(argv[2], NULL);
		cy = strtod(argv[3], NULL);
		flag = strtol(argv[4], NULL, 10);
		return ev->cmdSetPoint(cx, cy, flag);

	} else if ((c == 'd') && (strncmp(argv[1], "deleteObj", length) == 0)) { 
		cx = strtod(argv[2], NULL);
		cy = strtod(argv[3], NULL);
		return  ev->cmdDeleteObj(cx, cy);

	} else if ((c == 'm') && (strncmp(argv[1], "moveTo", length) == 0)) { 
		/*
		 * <view> moveto x y
		 * 
		 * Move the current selected object (if any) or the 
		 * rubber band to point (x, y)
		 */
		if (argc != 4) {
			Tcl_AppendResult(tcl, "wrong # args: should be \"",
					 argv[0],
					 " moveTo x y\"", (char *)NULL);
			return (TCL_ERROR);
		}
		cx = strtod(argv[2], NULL);
		cy = strtod(argv[3], NULL);
		return ev->cmdMoveTo(cx, cy);

	} else if ((c == 'r') && (strncmp(argv[1], "relPoint", length) == 0)) {
		/*
		 * <view> relPoint x y
		 * 
		 * If any object is selected, set the object's
		 * position to point (x,y); otherwise set the rubber
		 * band rectangle and select all the objects in that
		 * rectangle Note: we need a default tag for the
		 * selection in rubber band.  
		 */
		if (argc != 4) {
			Tcl_AppendResult(tcl, "wrong # args: should be \"",
					 argv[0],
					 " relPoint x y\"", (char *)NULL);
			return (TCL_ERROR);
		}
		cx = strtod(argv[2], NULL);
		cy = strtod(argv[3], NULL);
		return ev->cmdReleasePoint(cx, cy);
	} else if ((c == 's') && (strncmp(argv[1], "setNodeProperty", length) == 0)) {
                /*
                 * <view> setNodeProperty nodeid nodepropertyvalue properyname
                 *
                 */
                int nodeid = atoi(argv[2]);
		int propertyname = atoi(argv[4]);

                return ev->cmdsetNodeProperty(nodeid, argv[3], propertyname);
	} else if ((c == 's') && (strncmp(argv[1], "setLinkProperty", length) == 0)) {
                /*
                 * <view> setLinkProperty sid did propertyvalue properyname
                 *
                 */
                int sid = atoi(argv[2]);
                int did = atoi(argv[3]);
		int propertyname = atoi(argv[5]);

                return ev->cmdsetLinkProperty(sid, did, argv[4], propertyname);

	} else if ((c == 'a') && (strncmp(argv[1], "addNode", length) == 0)) {
                /*
                 * <view> addNode x y
                 *
		 * add a new Node under current (x,y) with default size
                 */
                cx = strtod(argv[2], NULL);
                cy = strtod(argv[3], NULL);
                return ev->cmdaddNode(cx, cy);
	} else if ((c == 'a') && (strncmp(argv[1], "addLink", length) == 0)) {
                /*
                 * <view> addLink x y 
                 *
		 * add a new Link if its start AND end point is inside of 
                 */
                cx = strtod(argv[2], NULL);
                cy = strtod(argv[3], NULL);
                return ev->cmdaddLink(cx, cy);
	} else if ((c == 'g') && (strncmp(argv[1], "getObject", length) == 0)) {
                /*
                 * <view> getObject x y 
                 *
		 *  return obj under current point
                 */
                cx = strtod(argv[2], NULL);
                cy = strtod(argv[3], NULL);
                return ev->cmdgetCurrentObj(cx, cy);
	} else if ((c == 'g') && (strncmp(argv[1], "getObjectProperty", length) == 0)) {
                /*
                 * <view> getObjectProperty x y 
                 *
		 *  return obj property under current point
                 */
                cx = strtod(argv[2], NULL);
                cy = strtod(argv[3], NULL);
                return ev->cmdgetObjProperty(cx, cy);
	}

	return (View::command(cd, tcl, argc, argv));
}

int EditView::cmdsetNodeProperty(int id, char *pv, int pn)
{
	EditorNetModel* emodel_ = (EditorNetModel *)model_;	
	emodel_->setNodeProperty(id,pv,pn);

	EditType oldt_;
	oldt_ = objType_;
        objType_ = END_OBJECT;
        draw();
        objType_ = NONE;
        model_->update(model_->now());
        objType_ = oldt_;

	return(TCL_OK);
}

int EditView::cmdsetLinkProperty(int sid, int did, char *pv, int pn)
{
        EditorNetModel* emodel_ = (EditorNetModel *)model_;
        emodel_->setLinkProperty(sid,did, pv,pn);

        draw();

        return(TCL_OK);
}        


int EditView::cmdgetObjProperty(float cx, float cy)
{ 
	Tcl& tcl = Tcl::instance();
	matrix_.imap(cx, cy);
	Animation *p = model_->inside(cx, cy);
	if (p == NULL) {
	   tcl.resultf("NONE");
	} else {
	   tcl.resultf("%s",p->property());
 	}
	return(TCL_OK);	

}

int EditView::cmdgetCurrentObj(float cx, float cy)
{ 
	Tcl& tcl = Tcl::instance();
	matrix_.imap(cx, cy);
	Animation *p = model_->inside(cx, cy);
	if (p == NULL) {
	   tcl.resultf("NONE");
	} else {
	   tcl.resultf("%s",p->info());
 	}
	return(TCL_OK);	

}

// Note: all cx_ and cy_ below are in *window* coordinates

int EditView::cmdaddLink(float cx, float cy)
{
	Animation *p;
	static Node *src, *dst;
	EditorNetModel* emodel_ = (EditorNetModel *)model_;

	cx_ = cx, cy_ = cy;
        matrix_.imap(cx, cy);

	//Do we have a node  on current point ?
	p = model_->inside(cx, cy);

	if (p == NULL) {
            defTag_->remove();
//            startRubberBand(cx_, cy_);
	    if (objType_ == START_LINK) objType_ = NONE;
            return (TCL_OK);
        }        

        if (p->classid() == ClassNodeID) { 
	  if (objType_ != START_LINK) {
	      src = (Node *)p;
	      startSetObject(p, cx_, cy_);
	      objType_ = START_LINK;
	   } else {
	      // add a new link b/w the two nodes
	      startSetObject(p, cx_, cy_);

	      dst = (Node *)p;
	      emodel_->addLink(src,dst);
	      objType_ = END_OBJECT;

                // Erase old positions
                defTag_->draw(this, model_->now());
                // At least we should redo scale estimation and
                // place everything
                defTag_->move(this, rb_.xmax - oldx_, rb_.ymax - oldy_);
                model_->recalc();
                model_->render(this);
		defTag_->remove();
                objType_ = NONE;

	      draw();
	      objType_ = NONE; 

	   }
         } else {
	      objType_ = NONE;
	 } 

//	draw();
	return(TCL_OK);
}

// Note: all cx_ and cy_ below are in *window* coordinates
int EditView::cmdaddNode(float cx, float cy)
{
        matrix_.imap(cx, cy);

	EditorNetModel* emodel_ = (EditorNetModel *)model_;

  	emodel_->addNode(cx, cy);		
	draw();
	return(TCL_OK);
}

int EditView::cmdDeleteObj(float cx, float cy)
{
        // First of all, clean the old group  
        cx_ = cx, cy_ = cy;
                
        // Do we have anything on current point?
        matrix_.imap(cx, cy);
        Animation *p = model_->inside(cx, cy);
        if (p == NULL) {
                defTag_->remove();
                return (TCL_OK);
        }       
	
	// Only nodes and links can be deleted
	
	if (p->isTagged()) {
                if (p->numTag() > 1) {
                        fprintf(stderr,
                                "Error: More than one tags for object %d!\n",
                                p->id());
                        p = NULL;
                } else
                        p = p->getLastTag();
        }

	if ((p->classid() != ClassNodeID) && (p->classid() != ClassEdgeID))
                p = NULL;

	if (p == NULL) {
                defTag_->remove();
        } else {
	    if (p->classid() == ClassNodeID) {
		Node *n = (Node *)p;
		EditorNetModel* emodel_ = (EditorNetModel *)model_;
		emodel_ -> removeNode(n);

		draw();
		
	    } 
	    if (p->classid() == ClassEdgeID) {
		Edge *e = (Edge *)p;
		EditorNetModel* emodel_ = (EditorNetModel *)model_;
		emodel_ -> removeLink(e);
		draw();
	    } 
	}

	return (TCL_OK);

}

// Note: all cx_ and cy_ below are in *window* coordinates
int EditView::cmdSetPoint(float cx, float cy, int bAdd)
{
	// First of all, clean the old group
	cx_ = cx, cy_ = cy;

	// Do we have anything on current point?
	matrix_.imap(cx, cy);
	Animation *p = model_->inside(cx, cy);
	if (p == NULL) {
		defTag_->remove();
		startRubberBand(cx_, cy_);
		return (TCL_OK);
	}

	if (p->isTagged()) {
		if (p->numTag() > 1) {
			fprintf(stderr, 
				"Error: More than one tags for object %d!\n",
				p->id());
			p = NULL;
		} else
			p = p->getLastTag();
	}
	// Only nodes and tags can be moved
	if ((p->classid() != ClassNodeID) && (p->classid() != ClassTagID)) 
		p = NULL;

	if (p == NULL) {
		defTag_->remove();
		startRubberBand(cx_, cy_);
	} else {
		// If a single non-tag object is selected, or explicitly 
		// instructed, remove the previous selection
		if (!bAdd && (p != defTag_))
			defTag_->remove();
		startSetObject(p, cx_, cy_);
	}

	return (TCL_OK);
}

int EditView::cmdMoveTo(float cx, float cy)
{
	cx_ = cx, cy_ = cy;

	switch (objType_) {
	case START_RUBBERBAND:
	case MOVE_RUBBERBAND:
		oldx_ = rb_.xmax; oldy_ = rb_.ymax;
		rb_.xmax = cx_, rb_.ymax = cy_;
		objType_ = MOVE_RUBBERBAND;
		clip_ = rb_;
		clip_.adjust();
 		if (clip_.xmin > oldx_) 
 			clip_.xmin = oldx_;
 		if (clip_.xmax < oldx_) 
 			clip_.xmax = oldx_;
		if (clip_.ymin > oldy_) 
			clip_.ymin = oldy_;
		if (clip_.ymax < oldy_) 
			clip_.ymax = oldy_;
		break;
	case START_OBJECT:
	case MOVE_OBJECT: {
		oldx_ = rb_.xmax; oldy_ = rb_.ymax;
		rb_.xmax = cx_, rb_.ymax = cy_;
		objType_ = MOVE_OBJECT;
		clip_.clear();
		defTag_->merge(clip_);
		matrix_.map(clip_.xmin, clip_.ymin);
		matrix_.map(clip_.xmax, clip_.ymax);
		clip_.adjust();
		// Actual move and final bbox computation is done in render
		break;
	}
	default:
		// This cannot happen!
		fprintf(stderr, "moveTo without any selection. \
Please send your trace file to haoboy@isi.edu.\n");
		return (TCL_ERROR);
	}
	draw();
	return (TCL_OK);
}

int EditView::cmdReleasePoint(float cx, float cy)
{
	cx_ = cx, cy_ = cy;

	switch (objType_) {
	case START_RUBBERBAND:
	case MOVE_RUBBERBAND: {
		oldx_ = rb_.xmax; oldy_ = rb_.ymax;
		rb_.xmax = cx_, rb_.ymax = cy_;
		// Need to add the region to defTag_
		clip_ = rb_;
		clip_.adjust();
		BBox bb = clip_;
		matrix_.imap(bb.xmin, bb.ymin);
		matrix_.imap(bb.xmax, bb.ymax);
		bb.adjust();
		model_->tagArea(bb, defTag_, 1);

		// We can do a total redraw
		objType_ = NONE;
		draw();
		break;
	}
	case START_OBJECT:
	case MOVE_OBJECT: {
		oldx_ = rb_.xmax; oldy_ = rb_.ymax;
		rb_.xmax = cx_, rb_.ymax = cy_;
		clip_.clear();
		defTag_->merge(clip_);
		matrix_.map(clip_.xmin, clip_.ymin);
		matrix_.map(clip_.xmax, clip_.ymax);
		clip_.adjust();

		// Later in render() we'll compute the real bbox
		objType_ = END_OBJECT;
		draw();
		objType_ = NONE;
		model_->update(model_->now());
		break;
	}
	default:
		// This cannot happen!
//		fprintf(stderr, "moveTo without any selection. \
Please send your trace file to haoboy@isi.edu.\n");
		objType_ = NONE;
		draw();
//		return (TCL_ERROR);
		
	}
	return (TCL_OK);
}

void EditView::draw()
{
	if (objType_ == NONE)
		View::draw();
	else {
		// Ignore the cleaning part
		render();
		// XXX Don't understand why clip's height and width need to 
		// increase 3 to draw tagged objects correctly. 
		XCopyArea(Tk_Display(tk_), offscreen_, Tk_WindowId(tk_), 
			  background_,(int)clip_.xmin, (int)clip_.ymin, 
			  (int)clip_.width()+3, (int)clip_.height()+3, 
			  (int)clip_.xmin, (int)clip_.ymin);
	}
}

// Without transform.
void EditView::xrect(float x0, float y0, float x1, float y1, GC gc)
{
	int x = (int) floor(x0);
	int y = (int) floor(y0);
	int w = (int)(x1 - x0);
	if (w < 0) {
		x = (int) ceil(x1);
		w = -w;
	}
	int h = (int)(y1 - y0);
	if (h < 0) {
		h = -h;
		y = (int)ceil(y1);
	}
	XDrawRectangle(Tk_Display(tk_), offscreen_, gc, x, y, w, h);
}

void EditView::line(float x0, float y0, float x1, float y1, int color)
{
	if (objType_ != NONE)
		View::line(x0, y0, x1, y1, Paint::instance()->xor());
	else 
		View::line(x0, y0, x1, y1, color);
}

void EditView::rect(float x0, float y0, float x1, float y1, int color)
{
	if (objType_ != NONE)
		View::rect(x0, y0, x1, y1, Paint::instance()->xor());
	else 
		View::rect(x0, y0, x1, y1, color);
}

void EditView::polygon(const float* x, const float* y, int n, int color)
{
	if (objType_ != NONE)
		View::polygon(x, y, n, Paint::instance()->xor());
	else 
		View::polygon(x, y, n, color);
}

void EditView::fill(const float* x, const float* y, int n, int color)
{
	if (objType_ != NONE)
		View::fill(x, y, n, Paint::instance()->xor());
	else 
		View::fill(x, y, n, color);
}

void EditView::circle(float x, float y, float r, int color)
{
	if (objType_ != NONE)
		View::circle(x, y, r, Paint::instance()->xor());
	else 
		View::circle(x, y, r, color);
}

// Do not display any string, because no xor font gc
void EditView::string(float fx, float fy, float dim, const char* s, int anchor)
{
	if (objType_ == NONE)
		View::string(fx, fy, dim, s, anchor);
}

void EditView::render()
{
	// Here we can compute the clipping box for render
	Paint *paint = Paint::instance();
	GC gc = paint->paint_to_gc(paint->xor());

	switch (objType_) {
	case START_RUBBERBAND:
		// draw rubber band
		xrect(rb_.xmin, rb_.ymin, rb_.xmax, rb_.ymax, gc);
		break;

	case MOVE_RUBBERBAND: 
		// erase previous rubberband
		xrect(rb_.xmin, rb_.ymin, oldx_, oldy_, gc);
		// draw new rubberband
		xrect(rb_.xmin, rb_.ymin, rb_.xmax, rb_.ymax, gc);
		break;

	case END_RUBBERBAND: 
		// erase previous rubber band
		xrect(rb_.xmin, rb_.ymin, oldx_, oldy_, gc);
		// XXX Should draw the tag?
		model_->render(this);
		objType_ = NONE;
		break;

	case START_OBJECT:
		xrect(rb_.xmin, rb_.ymin, rb_.xmax, rb_.ymax, gc);
		// xor-draw all relevant objects
		defTag_->draw(this, model_->now());
		break;

	case MOVE_OBJECT: 
		// erase old positions first.
		if ((oldx_ == rb_.xmax) && (oldy_ == rb_.ymax)) 
			return;
		defTag_->draw(this, model_->now());
		// move these objects
		defTag_->move(this, rb_.xmax - oldx_, rb_.ymax - oldy_);
		BBox bb;
		bb.clear();
		defTag_->merge(bb);
		matrix_.imap(bb.xmin, bb.ymin);
		matrix_.imap(bb.xmax, bb.ymax);
		bb.adjust();
		clip_.merge(bb);
		defTag_->draw(this, model_->now());
		break;

	case END_OBJECT:
		// Erase old positions
		defTag_->draw(this, model_->now());
		// At least we should redo scale estimation and 
		// place everything
		defTag_->move(this, rb_.xmax - oldx_, rb_.ymax - oldy_);
		model_->recalc();
		model_->render(this);
		objType_ = NONE;
		break;

	default:
		// redraw model
		model_->render(this);
		return;
	}
}

