/*
 * Electric(tm) VLSI Design System
 *
 * File: routmimic.c
 * Arc mimic code for the wire routing tool
 * Written by: Steven M. Rubin, Static Free Software
 *
 * Copyright (c) 2000 Static Free Software.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 *
 * Static Free Software
 * 4119 Alpine Road
 * Portola Valley, California 94028
 * info@staticfreesoft.com
 */

#include "config.h"
#if ROUTTOOL

#include "global.h"
#include "rout.h"
#include "usr.h"

#define NOPOSSIBLEARC ((POSSIBLEARC *)-1)
#define LIKELYDIFFPORT     1
#define LIKELYDIFFNODETYPE 2
#define LIKELYDIFFNODESIZE 4

typedef struct Ipossiblearc
{
	ARCINST             *ai;
	NODEINST            *ni1, *ni2;
	PORTPROTO           *pp1, *pp2;
	INTBIG               x1, y1, x2, y2;
	INTBIG               situation;
	struct Ipossiblearc *nextpossiblearc;
} POSSIBLEARC;

static POSSIBLEARC *ro_mimicpossiblearcfree = NOPOSSIBLEARC;

static INTBIG       ro_totalmimicports = 0;
static POLYGON    **ro_mimicportpolys;

/* prototypes for local routines */
static void         ro_mimicdelete(ARCPROTO *typ, NODEINST **nodes, PORTPROTO **ports);
static INTBIG       ro_mimiccreate(ARCINST *arc);
static POSSIBLEARC *ro_allocpossiblearc(void);
static void         ro_freepossiblearc(POSSIBLEARC *pa);

/*
 * Routine to free all memory associated with this module.
 */
void ro_freemimicmemory(void)
{
	REGISTER POSSIBLEARC *pa;
	REGISTER INTBIG i;

	while (ro_mimicpossiblearcfree != NOPOSSIBLEARC)
	{
		pa = ro_mimicpossiblearcfree;
		ro_mimicpossiblearcfree = pa->nextpossiblearc;
		efree((char *)pa);
	}
	for(i=0; i<ro_totalmimicports; i++)
		freepolygon(ro_mimicportpolys[i]);
	if (ro_totalmimicports > 0) efree((char *)ro_mimicportpolys);
}

/*
 * Entry point for mimic router.  Called each "slice".  If "forced" is true,
 * this mimic operation was explicitly requested.
 */
void ro_mimicstitch(BOOLEAN forced)
{
	REGISTER ARCPROTO *ap;
	REGISTER ARCINST *ai;
	NODEINST *endnode[2];
	PORTPROTO *endport[2];
	REGISTER INTBIG options, i;
	float elapsed;

	/* if an arc was deleted, and that is being mimiced, do it */
	options = ro_getoptions();
	if (ro_deletedarc != NOARCPROTO && (options&MIMICUNROUTES) != 0)
	{
		for(i=0; i<2; i++)
		{
			endnode[i] = ro_deletednodes[i];
			endport[i] = ro_deletedports[i];
		}
		ap = ro_deletedarc;
		ro_deletedarc = NOARCPROTO;
		ro_mimicdelete(ap, endnode, endport);
	}

	/* if an arc was just created, mimic that */
	if (ro_createdarc != NOARCINST)
	{
		ai = ro_createdarc;
		ro_createdarc = NOARCINST;
		starttimer();
		if (ro_mimiccreate(ai) == 0)
		{
			/* nothing was wired */
			if (forced) ttyputmsg(_("No wires mimiced"));
		}
		elapsed = endtimer();
		if (elapsed > 60.0 && (us_useroptions&BEEPAFTERLONGJOB) != 0)
			ttybeep(1);
		if ((options&MIMICINTERACTIVE) == 0 && elapsed >= 2.0)
			ttyputmsg(_("Mimicing took %s"), explainduration(elapsed));
	}
}

/*
 * Routine to mimic the unrouting of an arc that ran from nodes[0]/ports[0] to
 * nodes[1]/ports[1] with type "typ".
 */
void ro_mimicdelete(ARCPROTO *typ, NODEINST **nodes, PORTPROTO **ports)
{
	REGISTER NODEPROTO *facet;
	REGISTER ARCINST *ai, *nextai;
	REGISTER INTBIG match, dist, thisdist, deleted, angle, thisangle;
	INTBIG x0, y0, x1, y1;

	/* determine length of deleted arc */
	portposition(nodes[0], ports[0], &x0, &y0);
	portposition(nodes[1], ports[1], &x1, &y1);
	dist = computedistance(x0, y0, x1, y1);
	if (dist != 0)
		angle = figureangle(x0, y0, x1, y1);

	/* look for a similar situation to delete */
	deleted = 0;
	facet = nodes[0]->parent;
	for(ai = facet->firstarcinst; ai != NOARCINST; ai = nextai)
	{
		nextai = ai->nextarcinst;

		/* arc must be of the same type */
		if (ai->proto != typ) continue;

		/* arc must connect to the same type of node/port */
		match = 0;
		if (ai->end[0].nodeinst->proto == nodes[0]->proto &&
			ai->end[1].nodeinst->proto == nodes[1]->proto &&
			ai->end[0].portarcinst->proto == ports[0] &&
			ai->end[1].portarcinst->proto == ports[1]) match = 1;
		if (ai->end[0].nodeinst->proto == nodes[1]->proto &&
			ai->end[1].nodeinst->proto == nodes[0]->proto &&
			ai->end[0].portarcinst->proto == ports[1] &&
			ai->end[1].portarcinst->proto == ports[0]) match = -1;
		if (match == 0) continue;

		/* must be the same length and angle */
		portposition(ai->end[0].nodeinst, ai->end[0].portarcinst->proto, &x0, &y0);
		portposition(ai->end[1].nodeinst, ai->end[1].portarcinst->proto, &x1, &y1);
		thisdist = computedistance(x0, y0, x1, y1);
		if (dist != thisdist) continue;
		if (dist != 0)
		{
			thisangle = figureangle(x0, y0, x1, y1);
			if ((angle%1800) != (thisangle%1800)) continue;
		}

		/* the same! delete it */
		startobjectchange((INTBIG)ai, VARCINST);
		(void)killarcinst(ai);
		deleted++;
	}
	if (deleted != 0)
		ttyputmsg(_("MIMIC ROUTING: deleted %ld %s"), deleted, makeplural(_("wire"), deleted));
}

/*
 * Routine to mimic the creation of arc "arc".  Returns the number of mimics made.
 */
INTBIG ro_mimiccreate(ARCINST *ai)
{
	REGISTER NODEPROTO *facet, *proto0, *proto1;
	REGISTER NODEINST *ni, *oni, *node0, *node1;
	REGISTER PORTPROTO *port0, *port1, *pp, *opp;
	REGISTER PORTARCINST *pi;
	REGISTER ARCINST *newai, *oai;
	REGISTER INTBIG dist, angle, options, count, i, wantx1, wanty1, end, end0offx, end0offy,
		situation, total, node0wid, node0hei, node1wid, node1hei, wid, w, hei,
		portpos, oportpos, stopcheck, ifignoreports, ifignorenodetype, ifignorenodesize;
	INTBIG x0, y0, x1, y1, x0c, y0c, desiredangle, existingangle, thisend, lx, hx, ly, hy;
	static POLYGON *poly = NOPOLYGON;
	REGISTER POLYGON *thispoly, **newportpolys;
	REGISTER POSSIBLEARC *firstpossiblearc, *pa;
	extern COMCOMP us_yesnop;
	char *pars[5], question[200];
#define NUMSITUATIONS 6
	static INTBIG situations[] = {
		0,														/* everything matches */
		LIKELYDIFFNODESIZE,										/* different node size */
		LIKELYDIFFPORT,											/* different ports */
		LIKELYDIFFPORT|LIKELYDIFFNODESIZE,						/* different port and node size */
		LIKELYDIFFNODETYPE|LIKELYDIFFPORT,						/* different ports and node type */
		LIKELYDIFFNODETYPE|LIKELYDIFFPORT|LIKELYDIFFNODESIZE	/* different ports, node type and size */
	};

	ttyputmsg(_("Mimicing last arc..."));

	/* get polygon */
	if (poly == NOPOLYGON) poly = allocstaticpolygon(5, ro_tool->cluster);

	facet = ai->parent;
	options = ro_getoptions();
	count = 0;
	stopcheck = 0;
	firstpossiblearc = NOPOSSIBLEARC;

	/* precompute information about every port in the facet */
	total = 0;
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			pp->temp1 = 0;
			for(i=0; pp->connects[i] != NOARCPROTO; i++)
				if (pp->connects[i] == ai->proto) break;
			if (pp->connects[i] == NOARCPROTO) continue;
			pp->temp1 = 1;
			total++;
		}
	}
	if (total == 0) return(count);
	if (total > ro_totalmimicports)
	{
		newportpolys = (POLYGON **)emalloc(total * (sizeof (POLYGON *)), ro_tool->cluster);
		for(i=0; i<ro_totalmimicports; i++)
			newportpolys[i] = ro_mimicportpolys[i];
		for(i=ro_totalmimicports; i<total; i++)
			newportpolys[i] = allocpolygon(4, ro_tool->cluster);
		if (ro_totalmimicports > 0) efree((char *)ro_mimicportpolys);
		ro_mimicportpolys = newportpolys;
		ro_totalmimicports = total;
	}
	i = 0;
	for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
	{
		for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
		{
			if (pp->temp1 == 0) continue;
			shapeportpoly(ni, pp, ro_mimicportpolys[i], FALSE);
			i++;
		}
	}
	if (stopping(STOPREASONROUTING)) return(count);

	/* search from both ends */
	for(end=0; end<2; end++)
	{
		node0 = ai->end[end].nodeinst;
		node1 = ai->end[1-end].nodeinst;
		proto0 = node0->proto;
		proto1 = node1->proto;
		port0 = ai->end[end].portarcinst->proto;
		port1 = ai->end[1-end].portarcinst->proto;
		x0 = ai->end[end].xpos;     y0 = ai->end[end].ypos;
		x1 = ai->end[1-end].xpos;   y1 = ai->end[1-end].ypos;
		dist = computedistance(x0, y0, x1, y1);
		if (dist != 0)
			angle = figureangle(x0, y0, x1, y1);
		portposition(node0, port0, &x0c, &y0c);
		end0offx = x0 - x0c;   end0offy = y0 - y0c;
		nodesizeoffset(node0, &lx, &ly, &hx, &hy);
		node0wid = node0->highx-hx - (node0->lowx+lx);
		node0hei = node0->highy-hy - (node0->lowy+ly);
		nodesizeoffset(node1, &lx, &ly, &hx, &hy);
		node1wid = node1->highx-hx - (node1->lowx+lx);
		node1hei = node1->highy-hy - (node1->lowy+ly);

		/* now search every node in the facet */
		portpos = 0;
		for(ni = facet->firstnodeinst; ni != NONODEINST; ni = ni->nextnodeinst)
		{
			for(pp = ni->proto->firstportproto; pp != NOPORTPROTO; pp = pp->nextportproto)
			{
				/* make sure the arc can connect */
				if (pp->temp1 == 0) continue;

				/* see if stopping is requested */
				stopcheck++;
				if ((stopcheck%50) == 0)
				{
					if (stopping(STOPREASONROUTING))
					{
						/* free memory for mimicing */
						while (firstpossiblearc != NOPOSSIBLEARC)
						{
							pa = firstpossiblearc;
							firstpossiblearc = pa->nextpossiblearc;
							ro_freepossiblearc(pa);
						}
						return(count);
					}
				}

				getcenter(ro_mimicportpolys[portpos], &x0, &y0);
				portpos++;
				x0 += end0offx;   y0 += end0offy;
				if (dist == 0)
				{
					wantx1 = x0;   wanty1 = y0;
				} else
				{
					wantx1 = x0 + mult(dist, cosine(angle));
					wanty1 = y0 + mult(dist, sine(angle));
				}

				/* now look for another node that matches the situation */
				oportpos = 0;
				for(oni = facet->firstnodeinst; oni != NONODEINST; oni = oni->nextnodeinst)
				{
					for(opp = oni->proto->firstportproto; opp != NOPORTPROTO; opp = opp->nextportproto)
					{
						/* make sure the arc can connect */
						if (opp->temp1 == 0) continue;
						thispoly = ro_mimicportpolys[oportpos];
						oportpos++;

						/*
						 * ensure that intra-node wirings stay that way
						 *
						 * it is tempting to move this code outside of the "opp" loop, but
						 * that breaks the ordering of precached polygons in "ro_mimicportpolys"
						 */
						if (node0 == node1)
						{
							if (ni != oni) continue;
						} else
						{
							if (ni == oni) continue;
						}

						/* don't replicate what is already done */
						if (ni == node0 && pp == port0 && oni == node1 && opp == port1) continue;

						/* see if they are the same distance apart */
						if (!isinside(wantx1, wanty1, thispoly)) continue;

						/* see if they are already wired */
						if (x0 == wantx1 && y0 == wanty1) desiredangle = -1; else
							desiredangle = figureangle(x0, y0, wantx1, wanty1);
						for(pi = ni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
						{
							if (pi->proto != pp) continue;
							oai = pi->conarcinst;
							if (desiredangle < 0)
							{
								if (oai->end[0].xpos == oai->end[1].xpos &&
									oai->end[0].ypos == oai->end[1].ypos) break;
							} else
							{
								if (oai->end[0].xpos == oai->end[1].xpos &&
									oai->end[0].ypos == oai->end[1].ypos) continue;
								if (oai->end[0].portarcinst == pi) thisend = 0; else thisend = 1;
								existingangle = figureangle(oai->end[thisend].xpos, oai->end[thisend].ypos,
									oai->end[1-thisend].xpos, oai->end[1-thisend].ypos);
								if (existingangle == desiredangle) break;
							}
						}
						if (pi != NOPORTARCINST) continue;

						if (x0 == wantx1 && y0 == wanty1) desiredangle = -1; else
							desiredangle = figureangle(wantx1, wanty1, x0, y0);
						for(pi = oni->firstportarcinst; pi != NOPORTARCINST; pi = pi->nextportarcinst)
						{
							if (pi->proto != opp) continue;
							oai = pi->conarcinst;
							if (desiredangle < 0)
							{
								if (oai->end[0].xpos == oai->end[1].xpos &&
									oai->end[0].ypos == oai->end[1].ypos) break;
							} else
							{
								if (oai->end[0].xpos == oai->end[1].xpos &&
									oai->end[0].ypos == oai->end[1].ypos) continue;
								if (oai->end[0].portarcinst == pi) thisend = 0; else thisend = 1;
								existingangle = figureangle(oai->end[thisend].xpos, oai->end[thisend].ypos,
									oai->end[1-thisend].xpos, oai->end[1-thisend].ypos);
								if (existingangle == desiredangle) break;
							}
						}
						if (pi != NOPORTARCINST) continue;

						/* figure out the wiring situation here */
						situation = 0;
						if (pp != port0 || opp != port1)
							situation |= LIKELYDIFFPORT;
						if (ni->proto != proto0 || oni->proto != proto1)
							situation |= LIKELYDIFFNODETYPE;
						nodesizeoffset(ni, &lx, &ly, &hx, &hy);
						wid = ni->highx-hx - (ni->lowx+lx);
						hei = ni->highy-hy - (ni->lowy+ly);
						if (wid != node0wid || hei != node0hei) situation |= LIKELYDIFFNODESIZE;
						nodesizeoffset(oni, &lx, &ly, &hx, &hy);
						wid = oni->highx-hx - (oni->lowx+lx);
						hei = oni->highy-hy - (oni->lowy+ly);
						if (wid != node1wid || hei != node1hei) situation |= LIKELYDIFFNODESIZE;

						/* see if this combination has already been considered */
						for(pa = firstpossiblearc; pa != NOPOSSIBLEARC; pa = pa->nextpossiblearc)
						{
							if (pa->ni1 == ni && pa->pp1 == pp && pa->ni2 == oni && pa->pp2 == opp)
								break;
							if (pa->ni2 == ni && pa->pp2 == pp && pa->ni1 == oni && pa->pp1 == opp)
								break;
						}
						if (pa != NOPOSSIBLEARC)
						{
							if (pa->situation == situation) continue;
							for(i=0; i<NUMSITUATIONS; i++)
							{
								if (pa->situation == situations[i]) break;
								if (situation == situations[i]) break;
							}
							if (pa->situation == situations[i]) continue;
						}
						if (pa == NOPOSSIBLEARC)
						{
							pa = ro_allocpossiblearc();
							pa->nextpossiblearc = firstpossiblearc;
							firstpossiblearc = pa;
						}
						pa->ai = ai;
						pa->ni1 = ni;      pa->pp1 = pp;
						pa->x1 = x0;       pa->y1 = y0;
						pa->ni2 = oni;     pa->pp2 = opp;
						pa->x2 = wantx1;   pa->y2 = wanty1;
						pa->situation = situation;
					}
				}
			}
		}
	}

	/* now create the mimiced arcs */
	ifignoreports = ifignorenodetype = ifignorenodesize = 0;
	for(i=0; i<NUMSITUATIONS; i++)
	{
		/* see if this situation is possible */
		total = 0;
		for(pa = firstpossiblearc; pa != NOPOSSIBLEARC; pa = pa->nextpossiblearc)
			if (pa->situation == situations[i]) total++;
		if (total == 0) continue;

		/* see if this situation is desired */
		if ((options&MIMICINTERACTIVE) == 0)
		{
			/* make sure this situation is the desired one */
			if ((options&MIMICIGNOREPORTS) == 0 && (situations[i]&LIKELYDIFFPORT) != 0)
			{
				ifignoreports += total;
				continue;
			}
			if ((options&MIMICIGNORENODETYPE) == 0 && (situations[i]&LIKELYDIFFNODETYPE) != 0)
			{
				ifignorenodetype += total;
				continue;
			}
			if ((options&MIMICIGNORENODESIZE) == 0 && (situations[i]&LIKELYDIFFNODESIZE) != 0)
			{
				ifignorenodesize += total;
				continue;
			}
		} else
		{
			/* show the wires to be created */
			(void)asktool(us_tool, "down-stack");
			(void)asktool(us_tool, "clear");
			for(pa = firstpossiblearc; pa != NOPOSSIBLEARC; pa = pa->nextpossiblearc)
			{
				if (pa->situation != situations[i]) continue;
				if (pa->x1 == pa->x2 && pa->y1 == pa->y2)
				{
					dist = el_curlib->lambda[el_curtech->techindex];
					(void)asktool(us_tool, "show-area", pa->x1-dist, pa->x1+dist, pa->y1-dist, pa->y1+dist,
						(INTBIG)facet);
				} else
				{
					(void)asktool(us_tool, "show-line", pa->x1, pa->y1, pa->x2, pa->y2, (INTBIG)facet);
				}
			}
			sprintf(question, _("Create %ld %s shown here?"), total, makeplural(_("wire"), total));
			total = ttygetparam(question, &us_yesnop, 5, pars);
			(void)asktool(us_tool, "up-stack");
			if (total > 0 && namesamen(pars[0], "no", strlen(pars[0])) == 0) continue;
		}

		/* make the wires */
		for(pa = firstpossiblearc; pa != NOPOSSIBLEARC; pa = pa->nextpossiblearc)
		{
			if (pa->situation != situations[i]) continue;
			wid = defaultarcwidth(pa->ai->proto);
			w = us_widestarcinst(pa->ai->proto, pa->ni1, pa->pp1);
			if (w > wid) wid = w;
			w = us_widestarcinst(pa->ai->proto, pa->ni2, pa->pp2);
			if (w > wid) wid = w;
			newai = newarcinst(pa->ai->proto, wid, pa->ai->userbits,
				pa->ni1, pa->pp1, pa->x1, pa->y1, pa->ni2, pa->pp2, pa->x2, pa->y2, facet);
			if (newai == NOARCINST)
			{
				ttyputerr(_("Problem creating arc"));
				return(count);
			}
			endobjectchange((INTBIG)newai, VARCINST);
			count++;
		}
		(void)asktool(us_tool, "flush-changes");
	}

	/* free memory for mimicing */
	while (firstpossiblearc != NOPOSSIBLEARC)
	{
		pa = firstpossiblearc;
		firstpossiblearc = pa->nextpossiblearc;
		ro_freepossiblearc(pa);
	}
	if (count != 0)
		ttyputmsg(_("MIMIC ROUTING: added %ld %s"), count, makeplural(_("wire"), count)); else
	{
		(void)initinfstr();
		(void)addstringtoinfstr(_("No wires added"));
		if (ifignoreports != 0)
			(void)formatinfstr(_(", would add %ld wires if 'ports must match' is off"), ifignoreports);
		if (ifignorenodetype != 0)
			(void)formatinfstr(_(", would add %ld wires if 'node types must match' is off"), ifignorenodetype);
		if (ifignorenodesize != 0)
			(void)formatinfstr(_(", would add %ld wires if 'nodes sizes must match' is off"), ifignorenodesize);
		ttyputmsg("%s", returninfstr());
	}
	return(count);
}

POSSIBLEARC *ro_allocpossiblearc(void)
{
	REGISTER POSSIBLEARC *pa;

	if (ro_mimicpossiblearcfree == NOPOSSIBLEARC)
	{
		pa = (POSSIBLEARC *)emalloc(sizeof (POSSIBLEARC), ro_tool->cluster);
		if (pa == 0) return(NOPOSSIBLEARC);
	} else
	{
		pa = ro_mimicpossiblearcfree;
		ro_mimicpossiblearcfree = pa->nextpossiblearc;
	}
	return(pa);
}

void ro_freepossiblearc(POSSIBLEARC *pa)
{
	pa->nextpossiblearc = ro_mimicpossiblearcfree;
	ro_mimicpossiblearcfree = pa;
}

#endif  /* ROUTTOOL - at top */
