/*****************************************************************************
*   Module to handle libraries (first part - file and io).		     *
*									     *
* Written by:  Gershon Elber			IBM PC Ver 1.0,	Oct. 1989    *
*****************************************************************************/

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#ifdef __MSDOS__
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <dir.h>
#include <alloc.h>
#else
#include <ctype.h>
char *strupr(char *s);
char *strlwr(char *s);
#endif /* __MSDOS__ */

#include "program.h"
#include "priorque.h"
#include "eelibs.h"
#include "eelibsl.h"
#include "eeredraw.h"
#include "igraph.h"

LibraryStruct *LibraryList = NULL;	    /* All part libs are saved here. */

static char *GetLine(FILE *f, char *Line, int *LineNum);
static PriorQue *LoadLibraryAux(FILE *f, int *NumOfParts);
static char *GetPinsEntry(FILE *f, char *Line, int *LineNum, int NumOfPins);
static int *GetMultiEntry(FILE *f, char *Line, int *LineNum, int NumOfUnits,
							int PinsPerUnit);
static LibraryDrawEntryStruct *GetDrawEntry(FILE *f, char *Line, int *LineNum,
			BooleanType *HasLines, LibraryEntryStruct *LibEnrty);
static void UpdateBBox(LibraryEntryStruct *LibEntry, int x, int y);
static LibraryStruct *FindLibrary(char *Name);

/*****************************************************************************
* Routine to load the given library name. FullLibName should hold full path  *
* of file name to open, while LibName should hold only its name.	     *
*****************************************************************************/
void LoadLibraryName(char *FullLibName, char *LibName)
{
    int NumOfParts;
    char Line[LINE_LEN_SHORT];
    FILE *f;
    LibraryStruct *NewLib;
    PriorQue *Entries;

    /* Dont reload same library twice: */
    if (FindLibrary(LibName) != NULL) return;

    f = fopen(FullLibName, "rt");
    if (f == NULL) {
	sprintf(Line, "Failed to open library \"%s\".\n", FullLibName);
	FatalError(Line);
    }

    fprintf(stderr, "Reading library \"%s\".\n", FullLibName);

    if ((Entries = LoadLibraryAux(f, &NumOfParts)) != NULL) {
	NewLib = (LibraryStruct *) MyMalloc(sizeof(LibraryStruct));
	NewLib -> Entries = Entries;
	NewLib -> NumOfParts = NumOfParts;
	strcpy(NewLib -> Name, LibName);
	NewLib -> Pnext = LibraryList;
	LibraryList = NewLib;
    }
    fclose(f);
}

/*****************************************************************************
* Routine to load all libraries specified lin LoadLibraryList which assumes  *
* to hold names of libraries to load, seperated by commas and with no lib    *
* extension. Note we dont use strtok inside the loop as LoadLibraryName uses *
* it internally.							     *
*****************************************************************************/
void LoadLibraries(char *LoadLibraryList)
{
    char *p,*pcc, *NextLibName, Name[LINE_LEN_SHORT],
	*LibName = LoadLibraryList;

    if (!strtok(LibName, "\n\r") || strlen(LibName) == 0) return;

    do {
	if ((NextLibName = strchr(LibName, ',')) != NULL) {
	    NextLibName[0] = 0;
	    NextLibName = &NextLibName[1];
	}
	strlwr(LibName);
	strcpy(Name, LibName);
	if ((p = strrchr(Name, '.')) != NULL && strlen(p) < 5) *p = 0;
	strcat(Name, ".lib");
	if ((p = searchpath(Name)) == NULL) {
	    char Line[LINE_LEN];

	 
		pcc = getenv("EEDLIB");
		if(pcc==NULL){
			fprintf(stderr,"EEDRAW: Libraries not found in search path, and EEDLIB not set\n");
			exit(1);
		}
		sprintf(Line,"%s\\%s",pcc,Name);
		
		p=Line;
	}

	LoadLibraryName(p, LibName);

	LibName = NextLibName;
    }
    while (LibName != NULL);
}

/*****************************************************************************
* Routine to read one line from given file.				     *
*****************************************************************************/
static char *GetLine(FILE *f, char *Line, int *LineNum)
{
    do {
	if (fgets(Line, LINE_LEN - 1, f) == NULL) return NULL;
	++*LineNum;
    }
    while (Line[0] == '#' || Line[0] == '\n' || Line[0] == 0);

    return Line;
}

/*****************************************************************************
* Routine to compare two LibraryEntryStruct for the PriorQue module.         *
* Comparison is based on Part name.					     *
*****************************************************************************/
int LibraryEntryCompare(LibraryEntryStruct *LE1,
			LibraryEntryStruct *LE2)
{
    return strcmp(LE1 -> Name, LE2 -> Name);
}

/*****************************************************************************
* Routine to load a library from given open file.			     *
*****************************************************************************/
static PriorQue *LoadLibraryAux(FILE *f, int *NumOfParts)
{
    int LineNum = 0;
    char Line[LINE_LEN], *p, *Name, *Prefix;
    BooleanType Res, HasLines;
    PriorQue
	*PQ = NULL;
    LibraryEntryStruct *LibEntry;

    *NumOfParts = 0;

    if (GetLine(f, Line, &LineNum) == NULL ||
	strncmp(Line, FILE_IDENT, sizeof(FILE_IDENT) - 1) != 0) {
	FatalError("File is NOT EEDRAW library!");
    }

    PQInit(&PQ);
    PQCompFunc((PQCompFuncType) LibraryEntryCompare);

    while (GetLine(f, Line, &LineNum)) {
	p = strtok(Line, " \t\n");

	if (strcmp(p, "DEF") != 0) {
	    sprintf(Line, "DEF command expected in line %d, aborted.",
								LineNum);
	    FatalError(Line);
	}
	else {
	    /* Read one DEF/ENDDEF part entry from library: */
	    LibEntry = (LibraryEntryStruct *)
					MyMalloc(sizeof(LibraryEntryStruct));
	    LibEntry -> BBoxMinX = LibEntry -> BBoxMaxX =
	    LibEntry -> BBoxMinY = LibEntry -> BBoxMaxY = 0;
	    LibEntry -> Pins = NULL;
	    LibEntry -> Drawings = NULL;
	    LibEntry -> Multi = NULL;

	    if ((Name = strtok(NULL, " \t\n")) == NULL ||      /* Part name: */
		(Prefix = strtok(NULL, " \t\n")) == NULL ||  /* Prefix name: */
		(p = strtok(NULL, " \t\n")) == NULL ||         /* NumOfPins: */
		sscanf(p, "%d", &LibEntry -> NumOfPins) != 1 ||
		(p = strtok(NULL, " \t\n")) == NULL ||        /* TextInside: */
		sscanf(p, "%d", &LibEntry -> TextInside) != 1 ||
		(p = strtok(NULL, " \t\n")) == NULL ||	        /* DrawNums: */
		sscanf(p, "%d", &LibEntry -> DrawNums) != 1 ||
		(p = strtok(NULL, " \t\n")) == NULL ||        /* NumOfUnits: */
		sscanf(p, "%d", &LibEntry -> NumOfUnits) != 1 ||
		(LibEntry -> NumOfUnits > 0 &&
		 ((p = strtok(NULL, " \t\n")) == NULL ||     /* PinsPerUnit: */
		   sscanf(p, "%d", &LibEntry -> PinsPerUnit) != 1)) ||
		strlen(Name) > PART_NAME_LEN ||
		strlen(Prefix) > PREFIX_NAME_LEN) {
		sprintf(Line, "Wrong DEF format in line %d, aborted.",
								LineNum);
		FatalError(Line);
	    }
	    else {
		/* Copy part name and prefix. */
		strupr(Name);
		if ((LibEntry -> DrawName = Name[0] != '~') != FALSE)
		    strcpy(LibEntry -> Name, Name);
		else
		    strcpy(LibEntry -> Name, &Name[1]);
		if (strcmp(Prefix, "~") == 0)
		    LibEntry -> Prefix[0] = 0;
		else
		    strcpy(LibEntry -> Prefix, Prefix);
	    }

	    HasLines = FALSE;
	    while (TRUE) {
		GetLine(f, Line, &LineNum);
		p = strtok(Line, " \t\n");

		Res = TRUE;
		if (strcmp(p, "ENDDEF") == 0) {
		    ++*NumOfParts;
		    break;
		}
		else if (strcmp(p, "DRAW") == 0)
		    Res = (LibEntry -> Drawings =
			   GetDrawEntry(f, Line, &LineNum, &HasLines,
							   LibEntry)) != NULL;
		else if (strcmp(p, "PINS") == 0)
		    Res = (LibEntry -> Pins =
			   GetPinsEntry(f, Line, &LineNum,
					LibEntry -> NumOfPins)) != NULL;
		else if (strcmp(p, "MULTI") == 0) {
		    if (LibEntry -> NumOfUnits > 0)
			Res = (LibEntry -> Multi =
			       GetMultiEntry(f, Line, &LineNum,
					     LibEntry -> NumOfUnits,
					     LibEntry -> PinsPerUnit)) != NULL;
		    else {
			sprintf(Line, "MULTI found in DEF with #Units = 0 in line %d, aborted.",
								LineNum);
			FatalError(Line);
		    }
		}
		else {
		    sprintf(Line, "Undefined command \"%s\" in line %d, aborted.",
								p, LineNum);
		    FatalError(Line);
		}

		if (!Res) {		      /* Something went wrong there. */
		    FatalError("Wrong format");        /* Should die before! */
		}
	    }
	    if (LibEntry -> Pins == NULL && HasLines) {
		sprintf(Line, "No PINS defined for part in line %d, aborted.",
								LineNum);
		FatalError(Line);
	    }
	    if (LibEntry -> NumOfUnits > 0) {
		if (LibEntry -> Multi == NULL) {
		    sprintf(Line, "No MULTI defined for part in line %d, aborted.",
								LineNum);
		    FatalError(Line);
		}
		if (LibEntry -> Drawings == NULL) {
		    sprintf(Line, "No DRAW defined for part in line %d, aborted.",
								LineNum);
		    FatalError(Line);
		}
	    }
	    if (LibEntry -> Drawings == NULL) {
		/* Initialize BBox manually - its only a box with pins: */
		LibEntry -> BBoxMaxX = LibEntry -> BBoxMaxY =
		   PIN_WIDTH * LibEntry -> NumOfPins / 2 + PIN_LENGTH;
		LibEntry -> BBoxMinX = LibEntry -> BBoxMinY =
		    -LibEntry -> BBoxMaxX;
	    }

	    /* If we are here, this part is O.k. - put it in: */
	    PQInsert(&PQ, (VoidPtr) LibEntry);
	}
    }

    return PQ;
}

/*****************************************************************************
* Routine to load a PINS definition from given file. Note "PINS" line has    *
* been read already. Reads upto and include ENDPINS, or an error (NULL ret). *
*****************************************************************************/
static char *GetPinsEntry(FILE *f, char *Line, int *LineNum, int NumOfPins)
{
    int i;
    char *p, Pins[LINE_LEN];

    Pins[0] = 0;
    for (i = 0; i < NumOfPins; i++) {
	if (GetLine(f, Line, LineNum) == NULL) {
	    FatalError("File ended prematurely");
	}

	p = strtok(Line, "\n");		     /* Remove the CR from line end. */
	if (strcmp(p, "ENDPINS") == 0) {
	    sprintf(Line, "ENDPINS too soon (not enough pins) in line %d, aborted.", *LineNum);
	    FatalError(Line);
	}
	if ((p[0] == '~' && strlen(p) == 1))	       /* Empty line ("~") ? */
	    strcat(Pins, "~");
	else
	    strcat(Pins, p);
	strcat(Pins, PIN_SEPERATOR);
	if (strlen(Pins) > LINE_LEN - 10) {
	    sprintf(Line, "Pin definitions are too long in line %d, aborted.",
								*LineNum);
	    FatalError(Line);
	}
    }

    if (GetLine(f, Line, LineNum) == NULL) {
	FatalError("File ended prematurely");
    }
    p = strtok(Line, " \t\n");
    if (strcmp(p, "ENDPINS") != 0) {
	sprintf(Line, "ENDPINS expected in line %d, aborted.", *LineNum);
	FatalError(Line);
    }

    return strdup(Pins);
}

/*****************************************************************************
* Routine to load a MULTI definition from given file. Note "MULTI" line has  *
* been read already. Reads upto and include ENDMULTI, or an error (NULL ret).*
*****************************************************************************/
static int *GetMultiEntry(FILE *f, char *Line, int *LineNum, int NumOfUnits,
							int PinsPerUnit)
{
    int i, j, *Array,
	Count = 0;
    char *p;

    Array = (int *) MyMalloc(sizeof(int) * NumOfUnits * PinsPerUnit);

    for (i = 0; i < NumOfUnits; i++) {
	if (GetLine(f, Line, LineNum) == NULL) {
	    FatalError("File ended prematurely");
	}

	for (p = strtok(Line, " \t\n"), j = 0;
	     j < PinsPerUnit;
	     p = strtok(NULL, " \t\n"), j++) {
	    if (p == NULL ||
		sscanf(p, "%d", &Array[Count++]) != 1) {
		sprintf(Line, "MULTI has less pins than needed in line %d",
								*LineNum);
		FatalError(Line);
	    }
	}
    }

    if (GetLine(f, Line, LineNum) == NULL) {
	FatalError("File ended prematurely");
    }
    p = strtok(Line, " \t\n");
    if (strcmp(p, "ENDMULTI") != 0) {
	sprintf(Line, "ENDMULTI expected in line %d, aborted.", *LineNum);
	FatalError(Line);
    }

    return Array;
}

/*****************************************************************************
* Routine to load a DRAW definition from given file. Note "DRAW" line has    *
* been read already. Reads upto and include ENDDRAW, or an error (NULL ret). *
*****************************************************************************/
static LibraryDrawEntryStruct *GetDrawEntry(FILE *f, char *Line, int *LineNum,
			BooleanType *HasLines, LibraryEntryStruct *LibEntry)
{
    int i;
    char *p, Buffer[LINE_LEN_SHORT];
    RealType r;
    BooleanType
	Error = FALSE;
    LibraryDrawEntryStruct *Tail, *New,
	*Head = NULL;

    HasLines = FALSE;

    while (TRUE) {
	if (GetLine(f, Line, LineNum) == NULL) {
	    FatalError("File ended prematurely");
	}

	if (strncmp(Line, "ENDDRAW", 7) == 0) break;

	New = (LibraryDrawEntryStruct *)
				MyMalloc(sizeof(LibraryDrawEntryStruct));
	New -> Pnext = NULL;

	switch (Line[0]) {
	    case 'A': /* Arc */
		New -> DrawType = ARC_DRAW_TYPE;
		Error = sscanf(&Line[2], "%d %d %d %d %d %d", &New->Layer,
		    &New -> U.Arc.x, &New -> U.Arc.y, &New -> U.Arc.r,
		    &New -> U.Arc.t1, &New -> U.Arc.t2) != 6;
		New ->U.Arc.x *= LIB_SCALE_DRAW;
		New ->U.Arc.y *= LIB_SCALE_DRAW;
		New ->U.Arc.r *= LIB_SCALE_DRAW;
		NORMALIZE_ANGLE(New -> U.Arc.t1);
		NORMALIZE_ANGLE(New -> U.Arc.t2);
		if (New -> U.Arc.t1 > New -> U.Arc.t2)
		    New -> U.Arc.t2 += 360;
		New -> U.Arc.t1 += 1; /* Force arc of 180 degree to be less. */
		New -> U.Arc.t2 -= 1;

		/* Update the bbox: */
		UpdateBBox(LibEntry, New -> U.Arc.x - New -> U.Arc.r,
				     New -> U.Arc.y - New -> U.Arc.r);
		UpdateBBox(LibEntry, New -> U.Arc.x + New -> U.Arc.r,
				     New -> U.Arc.y + New -> U.Arc.r);
		break;
	    case 'C': /* Circle */
		New -> DrawType = CIRCLE_DRAW_TYPE;
		Error = sscanf(&Line[2], "%d %d %d %d", &New->Layer,
		    &New -> U.Circ.x, &New -> U.Circ.y, &New -> U.Circ.r) != 4;
		New ->U.Circ.x *= LIB_SCALE_DRAW;
		New ->U.Circ.y *= LIB_SCALE_DRAW;
		New ->U.Circ.r *= LIB_SCALE_DRAW;
		/* Update the bbox: */
		UpdateBBox(LibEntry, New -> U.Circ.x - New -> U.Circ.r,
				     New -> U.Circ.y - New -> U.Circ.r);
		UpdateBBox(LibEntry, New -> U.Circ.x + New -> U.Circ.r,
				     New -> U.Circ.y + New -> U.Circ.r);
		break;
	    case 'T': /* Text */
		New -> DrawType = TEXT_DRAW_TYPE;
		Error = sscanf(&Line[2], "%d %d %d %d %s", &New->Layer,
			       &New -> U.Text.x, &New -> U.Text.y,
			       &New -> U.Text.Horiz, Buffer) != 5;
		if (!Error) {			   /* Convert '~' to spaces. */
		    for (i = 0; i < strlen(Buffer); i++)
			if (Buffer[i] == '~') Buffer[i] = ' ';
		    New -> U.Text.Text = strdup(Buffer);
		}
		New ->U.Text.x *= LIB_SCALE_DRAW;
		New ->U.Text.y *= LIB_SCALE_DRAW;
		break;
	    case 'S': /* Square */
		New -> DrawType = SQUARE_DRAW_TYPE;
		Error = sscanf(&Line[2], "%d %d %d %d %d", &New->Layer,
			       &New -> U.Sqr.x1, &New -> U.Sqr.y1,
			       &New -> U.Sqr.x2, &New -> U.Sqr.y2) != 5;
		New ->U.Sqr.x1 *= LIB_SCALE_DRAW;
		New ->U.Sqr.y1 *= LIB_SCALE_DRAW;
		New ->U.Sqr.x2 *= LIB_SCALE_DRAW;
		New ->U.Sqr.y2 *= LIB_SCALE_DRAW;
		break;
	    case 'L': /* Line */
		*HasLines = TRUE;
		New -> DrawType = LINE_DRAW_TYPE;
		if ((i = sscanf(&Line[2], "%d %d %d %d %d %s",&New->Layer,
				&New -> U.Line.x1, &New -> U.Line.y1,
				&New -> U.Line.x2, &New -> U.Line.y2,
				Buffer)) != 6)
		    i = sscanf(&Line[2], "%d %d %d %d %d",&New->Layer,
			       &New -> U.Line.x1, &New -> U.Line.y1,
			       &New -> U.Line.x2, &New -> U.Line.y2);
		Error = (i != 5 && i != 6) ||
			(New -> U.Line.x1 != New -> U.Line.x2 &&
			 New -> U.Line.y1 != New -> U.Line.y2);
		New ->U.Line.x1 *= LIB_SCALE_DRAW;
		New ->U.Line.y1 *= LIB_SCALE_DRAW;
		New ->U.Line.x2 *= LIB_SCALE_DRAW;
		New ->U.Line.y2 *= LIB_SCALE_DRAW;
		UpdateBBox(LibEntry, New -> U.Line.x1, New -> U.Line.y1);
		UpdateBBox(LibEntry, New -> U.Line.x2, New -> U.Line.y2);
		New -> U.Line.Invert = i == 5 && Buffer[0] == 'I';
		break;
	    case 'P': /* Polyline */
		New -> DrawType = POLYLINE_DRAW_TYPE;
		New -> U.Poly.PolyList = NULL;
		p = strtok(&Line[4], " \t\n");
		if (sscanf(&Line[2], "%d %d",
			&New->Layer, &New -> U.Poly.n) == 2 &&
		    New -> U.Poly.n > 0) {
		    New -> U.Poly.PolyList = (int *)
			MyMalloc(sizeof(int) * New -> U.Poly.n * 2);
		    for (i = 0; i < New -> U.Poly.n * 2 && !Error; i++) {
			p = strtok(NULL, " \t\n");
			Error = sscanf(p, "%d", &New -> U.Poly.PolyList[i]) !=
									1;
			New ->U.Poly.PolyList[i] *= LIB_SCALE_DRAW;
			if (i % 2 != 0)
			    UpdateBBox(LibEntry, New -> U.Poly.PolyList[i-1],
						 New -> U.Poly.PolyList[i]);
		    }
		    New -> U.Poly.Fill = (p = strtok(NULL, " \t\n")) != NULL &&
					 p[0] == 'F';
		}
		else
		    Error = TRUE;
		Error |= New -> U.Poly.Fill &&
		    (New -> U.Poly.PolyList[0] != New -> U.Poly.PolyList[i-2] ||
		     New -> U.Poly.PolyList[1] != New -> U.Poly.PolyList[i-1]);
		if (Error && New -> U.Poly.PolyList)
		    MyFree((VoidPtr) New -> U.Poly.PolyList);
		break;
	    default:
		sprintf(Line, "Undefined DRAW command in line %d, aborted.",
								*LineNum);
		FatalError(Line);
	}
	if (Error) {
	    sprintf(Line, "Error in %c DRAW command in line %d, aborted.",
							Line[0], *LineNum);
	    FatalError(Line);
	}
	else {
	    if (Head == NULL)
		Head = Tail = New;
	    else {
		Tail -> Pnext = New;
		Tail = New;
	    }
	}
    }

    /* Update the bbox to the maximum extrem as we may rotate the object. */
    r = MAX(ABS(LibEntry->BBoxMinX), ABS(LibEntry->BBoxMaxX));
    r = MAX(r, ABS(LibEntry->BBoxMinY));
    r = MAX(r, ABS(LibEntry->BBoxMaxY));
    LibEntry->BBoxMaxX = LibEntry->BBoxMaxY = r;
    LibEntry->BBoxMinX = LibEntry->BBoxMinY = -r;

    return Head;
}

/*****************************************************************************
* Routine to update LibEntry bounding box accrding to given x, y value.	     *
*****************************************************************************/
static void UpdateBBox(LibraryEntryStruct *LibEntry, int x, int y)
{
    if (LibEntry -> BBoxMinX > x) LibEntry -> BBoxMinX = x;
    if (LibEntry -> BBoxMaxX < x) LibEntry -> BBoxMaxX = x;
    if (LibEntry -> BBoxMinY > y) LibEntry -> BBoxMinY = y;
    if (LibEntry -> BBoxMaxY < y) LibEntry -> BBoxMaxY = y;
}

/*****************************************************************************
* Routine to find the library given its name.				     *
*****************************************************************************/
static LibraryStruct *FindLibrary(char *Name)
{
    LibraryStruct
	*Lib = LibraryList;

    while (Lib) {
	if (strcmp(Name, Lib -> Name) == 0) return Lib;
	Lib = Lib -> Pnext;
    }
    return NULL;
}

#ifndef __MSDOS__

/*****************************************************************************
* Routine to convert a string to all upper case:			     *
*****************************************************************************/
char *strupr(char *s)
{
    while (*s) {
	if (islower(*s)) *s = toupper(*s);
	s++;
    }

    return s;
}

/*****************************************************************************
* Routine to convert a string to all upper case:			     *
*****************************************************************************/
char *strlwr(char *s)
{
    while (*s) {
	if (isupper(*s)) *s = tolower(*s);
	s++;
    }

    return s;
}

#endif /* __MSDOS__ */
