/*
 *                            COPYRIGHT
 *
 *  PCB, interactive printed circuit board design
 *  Copyright (C) 1994,1995,1996 Thomas Nau
 * 
 *  This module, rats.c, was written and is Copyright (C) 1997 by harry eaton
 *  this module is also subject the GNU GPL as described below
 *
 *  This program 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.
 *
 *  This program 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 this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* Change History:
 * Started 6/10/97
 * Added support for minimum length rat lines 6/13/97
 */ 

/* rats nest routines
 */
#include <math.h>
#include <stdio.h>

#include "global.h"

#include "create.h"
#include "data.h"
#include "draw.h"
#include "error.h"
#include "file.h"
#include "find.h"
#include "misc.h"
#include "mymem.h"
#include "rats.h"
#include "search.h"
#include "set.h"
#include "undo.h"

#define TRIEDFIRST 0x1
#define BESTFOUND 0x2

/* ---------------------------------------------------------------------------
 * some forward declarations
 */
static	int 		ReadRatLineFromFile(FILE *);
static	FILE		*PipeRat(char *);
static	Boolean		FindPad(char *, Cardinal);
static	int		ParseConnection(char *, char *, int *, Boolean *);
static	Boolean		DrawShortestRats(NetTypePtr);
static	Boolean		GatherSubnets(NetTypePtr, Boolean);

/* ---------------------------------------------------------------------------
 * some local identifiers
 */
static	DynamicStringType	RatFileLine;
static	ConnectionType		LastPoint;
static	NetTypePtr		Netlist = NULL;
static	Boolean			badnet = False;

/* ---------------------------------------------------------------------------
 * parse a connection description from a string
 * puts the element name in the string and the pin number in
 * the number.  If a valid connection is found, it returns the
 * number of characters processed from the string, otherwise
 * it returns 0
 */
static int ParseConnection(char *InString, char *ElementName, int *PinNum, Boolean *netname)
{
	int	i, j, result = 0;
	char	snippet[256];

	while (InString[result] && InString[result] != '\n')
	{
		i = 0;
			/* skip leading separator if necessary */
		while (InString[result] == ' ' || InString[result] == '\t' || InString[result] == '\n'
				|| InString[result] == '\\')
			result++;
			/* copy field into snippet */
		while (InString[result] && InString[result] != ' ' && InString[result] != '\t'
			    && InString[result] != '\n' && InString[result] !='\\'
			    && i < 256)
			snippet[i++] = InString[result++];
		snippet[i] = '\0';
			/* skip separator */
		while (InString[result] == ' ' || InString[result] == '\t' || InString[result] == '\n'
				|| InString[result] == '\\')
			result++;
			/* copy element name portion */
		for (j = 0; (j < i) && (snippet[j] != '-');  j++)
			ElementName[j] = snippet[j];
		if (*netname)
		{
			*netname = False;
		}
		else if (snippet[j] == '-')
		{
			for (i = j; i > 0 && ElementName[i-1] >= 'a'; i--);
			ElementName[i] = '\0';
			*PinNum = atoi(&snippet[j+1]);
			return(result);
		}
		else
		{
			badnet = True;
			ElementName[j] = '\0';
			Message("Bad net-list format encountered near: \"%s\"\n",
				ElementName);
			return(0);
		}
	}
	return(0);
}
			

/* ---------------------------------------------------------------------------
 * open a pipe to read the rat's nest net-list file
 */
static FILE	*PipeRat(char *filename)
{
	static	char	*command = NULL;
	FILE			*thefp;
	
	MyFree(&command);
	command = EvaluateFilename(Settings.RatCommand, Settings.RatPath, filename, NULL);
	
		/* open pipe to stdout of command */
	if (*command == '\0' || (thefp = popen(command, "r")) == NULL)
	{
		PopenErrorMessage(command);
	}
		return(thefp);
}

/* ---------------------------------------------------------------------------
 * reads a line from the rats nest netlist file
 */
static int ReadRatLineFromFile(FILE *fp)
{
	int count = 0;
	int c;

	DSClearString(&RatFileLine);
	while (((c = getc(fp)) != EOF) && (c != '\n'))
	{
		DSAddCharacter(&RatFileLine, c);
		count ++;
	}
	return(count);
}

/* ---------------------------------------------------------------------------
 * Find a particular pad from an element name and pin number
 */
static	Boolean	FindPad(char *ElementName, Cardinal PinNum)
{
	ElementTypePtr	element;
	
	if ((element = SearchElementByName(PCB->Data, ElementName)) != NULL)
	{
		if ((PinNum < 1) || (PinNum > MAX(element->PadN, element->PinN)))
			return(False);
		LastPoint.PinNum = PinNum;
		LastPoint.Reachable = True;
		if (element->PinN > element->PadN)
		{
			LastPoint.Object = &element->Pin[PinNum -1];
			LastPoint.Element = element;
		}
		else
		{
			LastPoint.Object = (PinTypePtr)
					    &element->Pad[PinNum - 1];
			LastPoint.Element = element;
		}
		return(True);
	}
	return(False);
}

/* ---------------------------------------------------------------------------
 * Read in a net list and store it in Netlist
 */
 
 void	ReadNetlist(char *filename)
 {
 	FILE			*fp;
 	int			lineLength;
	static	Cardinal	PinNum;
	char			ElementName[256];
 	int			i, n, lines;
	ConnectionTypePtr	connection;
	SubnetTypePtr		subnet;
 	Boolean			continued, netname;
 	
	if (Netlist || !filename)
		return;		/* nothing to do */
 	if ((fp = PipeRat(filename)) == NULL)
 		return;
	FreeNetlist();
	subnet = NULL;
	badnet = False;
	lines = 0;
	netname = True;
	Netlist = MyCalloc(1, sizeof(NetType), "ReadNetlist()");
	if (Netlist)
	{
 		while ((lineLength = ReadRatLineFromFile(fp)) != 0)
 		{
			lines++;
			i = 0;
			continued = (RatFileLine.Data[lineLength-1] == '\\') ? True : False;
			while ((i < lineLength) &&
		    	(n = ParseConnection(&RatFileLine.Data[i], ElementName, &PinNum, &netname)))
			{
				i += n;
				if (PinNum == 0)
				{
					Message("Pin numbered 0 encountered in netlist file\n"
						"Probably a bad netlist file format\n");
					badnet = True;
				}
 				else if (FindPad(ElementName, PinNum))
				{
					if (!subnet)
						subnet = GetSubnetMemory(Netlist);
					connection = GetConnectionMemory(subnet);
					*connection = LastPoint;
				}
				else
				{
 					Message("Can't find %s pin %d called for in netlist.\n",
						ElementName, PinNum);
					badnet = True;
				}
 			}
			if (!continued)
			{
				netname = True;
				subnet = NULL;
			}
		}
	}
	if (!lines)
	{
		Message("Empty netlist file!\n");
		badnet = True;
	}
	pclose(fp);
}

/*----------------------------------------------------------
 * FreeNetlist - Free the loaded memory for the netlist
 */
void FreeNetlist()
{
	if (Netlist)
	{
		NET_LOOP(Netlist, {
			SUBNET_LOOP(subnet, FreeSubnetMemory(subnet));
		});
		FreeNetMemory(Netlist);
		SaveFree(Netlist);
	}
	Netlist = NULL;
}

/* ---------------------------------------------------------------------------
 * Determine existing interconnections of the net and gather into sub-nets
 *
 */
static	Boolean		GatherSubnets(NetTypePtr Net, Boolean NoWarn)
{
	SubnetTypePtr		a,b;
	ConnectionTypePtr	conn;
	Cardinal		m,n;
	Boolean			CanReach,
				Warned = False;

	for (m = 0; Net->SubnetN > 0 && m < Net->SubnetN; m++)
	{
		a = &Net->Subnet[m];
		CanReach = a->Connection[0].Reachable;
		ResetFoundPinsViasAndPads();
		ResetFoundLinesAndPolygons();
		LookupConnection(a->Connection[0].X, a->Connection[0].Y, False);
			/* now anybody connected to the first point has FOUNDFLAG set */
			/* so move those to this subnet */
		CLEAR_FLAG(FOUNDFLAG, a->Connection[0].Object);
		for (n = m + 1; n < Net->SubnetN; n++) 
		{
			b = &Net->Subnet[n];
			if (TEST_FLAG(FOUNDFLAG, b->Connection[0].Object))
			{
				CLEAR_FLAG(FOUNDFLAG, b->Connection[0].Object);
				if (!CanReach)
					CanReach = b->Connection[0].Reachable;
			   /* There can be only one connection in subnet b */
				conn = GetConnectionMemory(a);
				*conn = b->Connection[0];
			   /* coppied connection from b to a, now delete b */
				FreeSubnetMemory(b);
			   /* and shrink the net accordingly */
				*b = Net->Subnet[--(Net->SubnetN)];
				memset(&Net->Subnet[Net->SubnetN], 0, sizeof(SubnetType));
			   /* back up since new subnet at old index */
				n--;
			}
		}
		if (!NoWarn)
		{
			ALLPAD_LOOP(PCB->Data, {
				if (TEST_FLAG(FOUNDFLAG, pad))
				{
					Message("WARNING!! Element %s pin %d is connected to net containing\n"
						"element %s pin %d, but it's not in the netlist!\n",
						element->Name[1].TextString, n+1,
						a->Connection[0].Element->Name[1].TextString,
						a->Connection[0].PinNum);
					SET_FLAG(WARNFLAG, pad);
					Warned = True;
					DrawPad(pad, 0);
				}
			});
			ALLPIN_LOOP(PCB->Data, {
				if (TEST_FLAG(FOUNDFLAG, pin))
				{
					Message("WARNING!! Element %s pin %d is connected to net containing\n"
						"element %s pin %d, but it's not in the netlist!\n",
						element->Name[1].TextString, n+1,
						a->Connection[0].Element->Name[1].TextString,
						a->Connection[0].PinNum);
					SET_FLAG(WARNFLAG, pin);
					Warned = True;
					DrawPin(pin, 0);
				}
			});
		}
		if (!CanReach && (Net->SubnetN > 1))
		{
			Message("Current layer can't reach subnet invovlving SMD %s pin %d\n",
				a->Connection[0].Element->Name[1].TextString,
				a->Connection[0].PinNum);
				/* remove unreachable nets so we don't try to draw them */
			FreeSubnetMemory(a);
			*a = Net->Subnet[--(Net->SubnetN)];
			memset(&Net->Subnet[Net->SubnetN], 0, sizeof(SubnetType));
				/* back up and do this again since a new */
				/* subnet has taken over the index */
			m--;
		}
	}
	ResetFoundPinsViasAndPads();
	ResetFoundLinesAndPolygons();
	return(Warned);
}

/* ---------------------------------------------------------------------------
 * Draw a rat net (tree) having the shortest lines
 * this also frees the subnet memory as they are consumed
 */

static	Boolean	DrawShortestRats(NetTypePtr Net)
{
 	LineTypePtr			line;
 	register float			distance, temp;
	register ConnectionTypePtr	conn1, conn2,
					firstpoint, secondpoint;
	Boolean				changed = False;
	Cardinal			n, m, j;
	SubnetTypePtr			next, subnet, theSubnet;

	while (Net->SubnetN > 1)
	{
	   subnet = &Net->Subnet[0];
	   distance = 5e32;
	   for (j = 1; j < Net->SubnetN; j++)
	   {
		next = &Net->Subnet[j];
		for (n = subnet->ConnectionN -1; n != -1; n--)
		{
			conn1 = &subnet->Connection[n];
			if (!conn1->Reachable)
				continue;
			for (m = next->ConnectionN -1; m != -1; m--)
			{
				conn2 = &next->Connection[m];
				if (!conn2->Reachable)
					continue;
				if ((temp = (conn1->X - conn2->X)*(conn1->X - conn2->X) +
				    (conn1->Y - conn2->Y)*(conn1->Y - conn2->Y)) < distance)
				{
					distance = temp;
					firstpoint = conn1;
					secondpoint = conn2;
					theSubnet = next;
				}
			}
		}
	   }
		/* found the shortest distance subnet, draw the rat */
	   if ((line = CreateNewLineOnLayer(CURRENT,
		firstpoint->X, firstpoint->Y, secondpoint->X, secondpoint->Y, 
		Settings.LineThickness, RATFLAG)) != NULL)
	   {
		AddObjectToCreateUndoList(LINE_TYPE, CURRENT, line, line);
		DrawLine(CURRENT, line, 0);
		changed = True;
	   }
	   
			/* copy the subnet into the current subnet */
	   for (m = theSubnet->ConnectionN -1; m != -1; m--)
	   {
		conn1 = GetConnectionMemory(subnet);
		*conn1 = theSubnet->Connection[m];
	   }
	   FreeSubnetMemory(theSubnet);
	   *theSubnet = Net->Subnet[--(Net->SubnetN)];
	   memset(&Net->Subnet[Net->SubnetN], 0, sizeof(SubnetType));
	}
		/* presently nothing to do with the new subnet */
		/* so we throw it away and free the space */
	FreeSubnetMemory(&Net->Subnet[--(Net->SubnetN)]);
	memset(&Net->Subnet[Net->SubnetN], 0, sizeof(SubnetType));
	return(changed);
}


/* ---------------------------------------------------------------------------
 *  AddAllRats puts the rats nest into the layout from the loaded netlist
 *  if SelectedOnly is true, it will only draw rats to selected pins and pads
 */
Boolean AddAllRats(Boolean SelectedOnly)
{
	NetTypePtr		Net;
	SubnetTypePtr		lonesome;
	ConnectionTypePtr	onepin;
	int			flag;
	Boolean		changed, Warned = False;
	Cardinal	test, group = GetLayerGroupNumberByPointer(CURRENT);

	ReadNetlist(PCB->Netlistname);
	if (!Netlist)
	{
		Message("Can't add rat lines because no netlist is loaded.\n");
		return(False);
	}
	changed = False;
		/* first we get the netlist into a useable form */
	Net = MyCalloc(1, sizeof(NetType), "AddAllRats()");
	NET_LOOP(Netlist, {
		SUBNET_LOOP(subnet, {
			if (!SelectedOnly || TEST_FLAG(SELECTEDFLAG, connection->Object))
			{
				lonesome = GetSubnetMemory(Net);
				onepin = GetConnectionMemory(lonesome);
				*onepin = *connection;
				if (TEST_FLAG(PINFLAG, onepin->Object))
				{
					onepin->X = onepin->Object->X;
					onepin->Y = onepin->Object->Y;
				}
				else
				{
					/* this is a pad, see if we can reach it */
					onepin->X = ((PadTypePtr) (onepin->Object))->Point1.X;
					onepin->Y = ((PadTypePtr) (onepin->Object))->Point1.Y;
					test = GetLayerGroupNumberByNumber(MAX_LAYER +
						(TEST_FLAG(ONSOLDERFLAG, onepin->Object) ?
						SOLDER_LAYER : COMPONENT_LAYER));
					onepin->Reachable = (test == group);
				}
			}
		});
		Warned |= GatherSubnets(Net, SelectedOnly);
		if (Net->SubnetN > 0)
			changed |= DrawShortestRats(Net);
	});
	NET_LOOP(Net, FreeSubnetMemory(subnet));
	FreeNetMemory(Net);
	SaveFree(Net);
	if (Warned)
		Settings.RatWarn = True;
	if (changed)
	{
		IncrementUndoSerialNumber();
		return(True);
	}
	if (!SelectedOnly && !Warned && !badnet)
	{
		flag = 0;
		ALLLINE_LOOP(PCB->Data, flag |= line->Flags);
		if ((flag & RATFLAG) == NOFLAG)
			Message("Congratulations!!\n"
			  "The layout is complete!\n"
			  "And has no shorted nets.\n");
		else
			Message("Nothing more to add, but there are\n"
				"rat-lines presently in the layout.\n");
	}
	return(False);
}
