/*
 *                            COPYRIGHT
 *
 *  PCB, interactive printed circuit board design
 *  Copyright (C) 1994,1995,1996 Thomas Nau
 *
 *  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.
 *
 *  Contact addresses for paper mail and Email:
 *  Thomas Nau, Schlehenweg 15, 88471 Baustetten, Germany
 *  Thomas.Nau@rz.uni-ulm.de
 *
 */

/* Change History:
 * 10/11/96 11:37 AJF Added support for a Text() driver function.
 * This was done out of a pressing need to force text to be printed on the
 * silkscreen layer. Perhaps the design is not the best.
 */ 

static	char	*rcsid = "$Id: print.c,v 145.1 1997/07/26 12:56:27 nau Exp $";

/* printing routines
 */
#include <math.h>
#include <time.h>

#include "global.h"

#include "data.h"
#include "dev_ps.h"
/*
  #include "dev_gerber.h"
  #include "dev_gerberX.h"
*/
#include "dev_rs274x.h"
#include "drill.h"
#include "file.h"
#include "error.h"
#include "misc.h"
#include "print.h"

/* ---------------------------------------------------------------------------
 * some local identifiers
 */
static	PrintDeviceTypePtr	Device;
static	PrintInitType		DeviceFlags;
static	Boolean			GlobalOutlineFlag,	/* copy of local ident. */
				GlobalAlignmentFlag,
				GlobalDrillHelperFlag,
				GlobalColorFlag,
				GlobalDOSFlag,
				ReplaceOK;		/* ok two overriode all */
/* print output files */
static	char			*GlobalCommand;

/* ---------------------------------------------------------------------------
 * some local prototypes
 */
static	void	SetPrintColor(Pixel, int);
static	FILE	*OpenPrintFile(char *);
static	int	SetupPrintFile(char *, char *);
static	int	ClosePrintFile(void);
static	int	FPrintOutline(int);
static	int	FPrintAlignment(int);
static	int	PrintLayergroups(Boolean, int);
static	int	PrintSilkscreen(Boolean, int);
static	int	PrintDrill(int);
static	int	PrintMask(int);

/* ----------------------------------------------------------------------
 * queries color from X11 database and calls device  command
 * black is default on errors
 */
static void SetPrintColor(Pixel X11Color, int GBAFlag)
{
	XColor	rgb;
	int	result;

	/* do nothing if no colors are requested */
	if (!GlobalColorFlag)
		return;

	/* query best matching color from X server */
	rgb.pixel = X11Color;
	result = XQueryColor(Dpy,
		DefaultColormapOfScreen(XtScreen(Output.Toplevel)),
		&rgb);

	/* create a PS command to set this color */
	if (result == BadValue || result == BadColor)
		rgb.red = rgb.green = rgb.blue = 0;
	Device->SetColor(rgb, GBAFlag);
}

/* ---------------------------------------------------------------------------
 * opens a file for printing
 */
static FILE *OpenPrintFile(char *FileExtention)
{
	char	*filename,
	*completeFilename;
	size_t	length;
	FILE	*fp;

	/* evaluate add extention and suffix to filename */
	if ((filename = ExpandFilename(NULL, GlobalCommand)) == NULL)
		filename = GlobalCommand;
	length = strlen(EMPTY(GlobalCommand)) +1 +
		strlen(FileExtention) +1 +strlen(Device->Suffix) +1;
	completeFilename = MyCalloc(length, sizeof(char), "OpenPrintFile()");
	sprintf(completeFilename, "%s%s%s.%s",
		GlobalDOSFlag ? "" : EMPTY(GlobalCommand),
		GlobalDOSFlag ? "" : "_",
		FileExtention, Device->Suffix);

	/* try to open all the file; if the value of
	 * 'ReplaceOK' is set to 'True' no more confirmation is
	 * requested for the sequence
	 */
	fp = CheckAndOpenFile(completeFilename, !ReplaceOK, True, &ReplaceOK);
	if (fp == NULL) {
		OpenErrorMessage(completeFilename);
		SaveFree(completeFilename);
		return(NULL);
	}
	SaveFree(completeFilename);
	return(fp);
}

/* ---------------------------------------------------------------------------
 * setup new print file
 */
static int SetupPrintFile(char *FileExtention, char *Description)
{
	char	*message;

	if ((DeviceFlags.FP = OpenPrintFile(FileExtention)) == NULL)
		return(1);
	if ((message = Device->Preamble(&DeviceFlags, Description)) != NULL) {
		Message(message);
		fclose(DeviceFlags.FP);
		return(1);
	}
	return(0);
}

/* ---------------------------------------------------------------------------
 * closes the printfile and calls the exit routine of the driver
 */
static int ClosePrintFile(void)
{
	Device->Postamble();
	return(fclose(DeviceFlags.FP));
}

/* ---------------------------------------------------------------------------
 * prints or draws outline to a given file
 */
static int FPrintOutline(int Gerber2Test)
{
	Device->Outline(0, 0, PCB->MaxWidth, PCB->MaxHeight, Gerber2Test);
	return(0);
}

/* ---------------------------------------------------------------------------
 * prints or draws alignment to a given file
 */
static int FPrintAlignment(int Gerber2Test)
{
	Device->Alignment(DeviceFlags.BoundingBox.X1,
		DeviceFlags.BoundingBox.Y1,
		DeviceFlags.BoundingBox.X2,
		DeviceFlags.BoundingBox.Y2,
		Gerber2Test);
	return(0);
}

/* ---------------------------------------------------------------------------
 * prints all layergroups
 * returns != zero on error
 */
static int PrintLayergroups(Boolean SilkscreenTextFlag, int Gerber2Test)
{
	char		extention[12],
			description[18];
	Cardinal	group,
			entry;

        ALLLINE_LOOP(PCB->Data, {
                if (TEST_FLAG(RATFLAG, line))
                {    
                        Message("WARNING!!  Rat lines in layout during printing!\n");
                        break;
                }
        });


  	for (group = 0; group < MAX_LAYER+2; group ++)
		if (PCB->LayerGroups.Number[group]) {
       			Cardinal	i;

			/* setup extention and open new file */
			sprintf(extention, "%s%i", GlobalDOSFlag ? "" : "group", group+1);
			sprintf(description, "layergroup #%i", group+1);

			if (SetupPrintFile(extention, description))
				return(1);

			/* print all layers in stack-order;
			 * loop oveal all stackposition anc check each layer
			 * for being a member of the group
			 */
			if (Gerber2Test == 1) {
				if (GlobalOutlineFlag)
					FPrintOutline(0);
				if (GlobalAlignmentFlag)
					FPrintAlignment(0);
				for (i = 0; i < MAX_LAYER; i++)
					for (entry = 0; entry < PCB->LayerGroups.Number[group]; entry++) {
						LayerTypePtr	layer;
						Cardinal	number;

						if ((number = PCB->LayerGroups.Entries[group][entry]) != i)
							continue;

					/* OK, found one; print it and break the look */
						layer = &PCB->Data->Layer[number];
						SetPrintColor(layer->Color, 0);
						Device->Layer(layer, group, SilkscreenTextFlag, 0);
						break;
					}

				/* check the component/solder layers for being members
				 * of this group. Print if TRUE
				 */
				for (entry = 0; entry < PCB->LayerGroups.Number[group]; entry++) {
					Cardinal	number;

					number = PCB->LayerGroups.Entries[group][entry] -MAX_LAYER;
					switch(number) {
						case COMPONENT_LAYER:
						case SOLDER_LAYER:
							SetPrintColor(PCB->PinColor, 0);
							ALLPAD_LOOP(PCB->Data,
								if ((TEST_FLAG(ONSOLDERFLAG, pad) != 0) ==
									(number == SOLDER_LAYER))
									Device->Pad(pad, 0););
							break;
						default:
							break;
					}
				}

				/* now at last all the pins/vias */
				SetPrintColor(PCB->PinColor, 0);
				ALLPIN_LOOP(PCB->Data,
					if (!TEST_FLAG(PININPOLYFLAG, pin))
					Device->PinOrVia(pin,0);
				);       
				SetPrintColor(PCB->ViaColor, 0);
				VIA_LOOP(PCB->Data,
					if (!TEST_FLAG(PININPOLYFLAG, via))
					Device->PinOrVia(via, 0);
				);
				ALLPIN_LOOP(PCB->Data,
					if (TEST_FLAG(PININPOLYFLAG, pin)) 
						Device->ClearPinOrViaOnGroundplane(pin, 0);
				);
				VIA_LOOP(PCB->Data,
					if (TEST_FLAG(PININPOLYFLAG, via))  
						Device->ClearPinOrViaOnGroundplane(via, 0);
				);      


				/* print drill-helper if requested */
				if (GlobalDrillHelperFlag) {
					SetPrintColor(PCB->PinColor, 0);
					ALLPIN_LOOP(PCB->Data, Device->DrillHelper(pin, 0););
					SetPrintColor(PCB->ViaColor, 0);
					VIA_LOOP(PCB->Data, Device->DrillHelper(via, 0););
				}
			}

			/* print all layers in stack-order;
			 * loop oveal all stackposition anc check each layer
			 * for beeing a member of the group
			 */
			if (GlobalOutlineFlag)
				FPrintOutline(1);
			if (GlobalAlignmentFlag)
				FPrintAlignment(1);

			for (i = 0; i < MAX_LAYER; i++)
				for (entry = 0; entry < PCB->LayerGroups.Number[group]; entry++) {
					LayerTypePtr	layer;
					Cardinal	number;

					if ((number = PCB->LayerGroups.Entries[group][entry]) != i)
						continue;

					/* OK, found one; print it and break the look */
					layer = &PCB->Data->Layer[number];
					SetPrintColor(layer->Color, 1);
					Device->Layer(layer, group, SilkscreenTextFlag, 1);
					break;
				}

			/* check the component/solder layers for being members
			 * of this group. Print if TRUE
			 */
			for (entry = 0; entry < PCB->LayerGroups.Number[group]; entry++) {
				Cardinal	number;

				number = PCB->LayerGroups.Entries[group][entry] -MAX_LAYER;
				switch(number) {
					case COMPONENT_LAYER:
					case SOLDER_LAYER:
						SetPrintColor(PCB->PinColor, 1);
						ALLPAD_LOOP(PCB->Data,
							if ((TEST_FLAG(ONSOLDERFLAG, pad) != 0) ==
								(number == SOLDER_LAYER))
								Device->Pad(pad, 1););
						break;
					default:
						break;
				}
			}

			/* now at last all the pins/vias */
			SetPrintColor(PCB->PinColor, 1);
			ALLPIN_LOOP(PCB->Data,
				if (!TEST_FLAG(PININPOLYFLAG, pin))
				Device->PinOrVia(pin,1);
			);       
			SetPrintColor(PCB->ViaColor, 1);
			VIA_LOOP(PCB->Data,
				if (!TEST_FLAG(PININPOLYFLAG, via))
				Device->PinOrVia(via, 1);
			);
			ALLPIN_LOOP(PCB->Data,
				if (TEST_FLAG(PININPOLYFLAG, pin)) 
					Device->ClearPinOrViaOnGroundplane(pin, 1);
			);
			VIA_LOOP(PCB->Data,
				if (TEST_FLAG(PININPOLYFLAG, via))  
					Device->ClearPinOrViaOnGroundplane(via, 1);
			);      

			/* print drill-helper if requested */
			if (GlobalDrillHelperFlag) {
				SetPrintColor(PCB->PinColor, 1);
				ALLPIN_LOOP(PCB->Data, Device->DrillHelper(pin, 1););
				SetPrintColor(PCB->ViaColor, 1);
				VIA_LOOP(PCB->Data, Device->DrillHelper(via, 1););
			}
			/* close the device */
			ClosePrintFile();
		}
	return(0);
}

/* ---------------------------------------------------------------------------
 * prints solder and component side
 * - first component followed by solder side
 * - first all element packages, followed by pads and pins
 * returns != zero on error
 */
static int PrintSilkscreen(Boolean SilkscreenTextFlag, int Gerber2Test)
{
	static	char	*extention[2] = { "componentsilk", "soldersilk" },
			*DOSextention[2] = { "cslk", "sslk" },
			*description[2] = {
				"silkscreen component side",
				"silkscreen solder side" };
	int		i;

	/* loop over both sides, start with component */
	for (i = 0; i < 2; i++) {
		/* start with the component side */
		if (SetupPrintFile(GlobalDOSFlag ? DOSextention[i] : extention[i],
			description[i]))
			return(1);

		/* print element outlines and canonical, instance or
		 * value descriptions
		 */
		if (Gerber2Test == 1) {
			if (GlobalOutlineFlag)
				FPrintOutline(0);
			if (GlobalAlignmentFlag)
				FPrintAlignment(0);

			SetPrintColor(PCB->ElementColor, 0);
			ELEMENT_LOOP(PCB->Data,
				if ((TEST_FLAG(ONSOLDERFLAG, element) == 0) == (i == 0))
					Device->ElementPackage(element, 0););
			/* Scan all layers of all groups and place any text in the
			 * silkscreen file that belongs there
			 */
			ALLTEXT_LOOP(PCB->Data, 
				if (TEST_FLAG(ONSILKFLAG, text) &&
				   ((TEST_FLAG(ONSOLDERFLAG, text) == 0)
				   == (i == 0)))
					Device->Text(text, 0);
			);
		}

		if (GlobalOutlineFlag)
			FPrintOutline(1);
		if (GlobalAlignmentFlag)
			FPrintAlignment(1);

		SetPrintColor(PCB->ElementColor, 1);
		ELEMENT_LOOP(PCB->Data,
			if ((TEST_FLAG(ONSOLDERFLAG, element) == 0) == (i == 0))
				Device->ElementPackage(element, 1);
		);

		/* Scan all layers of all groups and place any text in the
		 * silkscreen file that belongs there
		 */
		ALLTEXT_LOOP(PCB->Data, 
			if (TEST_FLAG(ONSILKFLAG, text) &&
			   ((TEST_FLAG(ONSOLDERFLAG, text) == 0)
			   == (i == 0)))
				Device->Text(text, 0);
		);
		ClosePrintFile();
	}
	return(0);
}

/* ---------------------------------------------------------------------------
 * creates a drill-information file if the device is able to
 * returns != zero on error
 */
static int PrintDrill(int Gerber2Test)
{
        DrillInfoTypePtr        AllDrills;
	int i;

	/* pass drilling information */
	if (Device->HandlesDrill) {
		if (SetupPrintFile(GlobalDOSFlag ? "dril" : "drill",
			"drill information"))
			return(1);

		if (Gerber2Test == 1) {
			if (GlobalOutlineFlag)
				FPrintOutline(0);
			if (GlobalAlignmentFlag)
				FPrintAlignment(0);

			SetPrintColor(PCB->PinColor, 0);
			ALLPIN_LOOP(PCB->Data, Device->Drill(pin, 0););
			VIA_LOOP(PCB->Data, Device->Drill(via, 0););
		}

		if (GlobalOutlineFlag)
			FPrintOutline(1);
		if (GlobalAlignmentFlag)
			FPrintAlignment(1);

		SetPrintColor(PCB->PinColor, 1);
                AllDrills = GetDrillInfo(PCB->Data);
                DRILL_LOOP(AllDrills,
                        for (i = 0; i < drill->PinN; i++)
                                Device->Drill(drill->Pin[i], 0);
                );
                        /* close the file */
                ClosePrintFile();
                FreeDrillInfo(AllDrills);
	}
	return(0);
}

/* ---------------------------------------------------------------------------
 * prints solder and component side mask
 * returns != zero on error
 */
static int PrintMask(int Gerber2Test)
{
	static	char	*extention[2] = { "componentmask", "soldermask" },
			*DOSextention[2] = { "cmsk", "smsk" },
			*description[2] = {
				"solder mask component side",
				"solder mask solder side" };
	int		i;
	BoxTypePtr	box;

	/* check capability */
	if (Device->HandlesMask) {
		box = GetDataBoundingBox(PCB->Data);

		/* loop over both sides, start with component */
		for (i = 0; i < 2; i++) {
			/* start with the component side */
			if (SetupPrintFile(GlobalDOSFlag ? DOSextention[i] : extention[i],
				description[i]))
				return(1);

			if (Gerber2Test == 1) {
				if (GlobalOutlineFlag)
					FPrintOutline(0);
				if (GlobalAlignmentFlag)
					FPrintAlignment(0);

				Device->FilledRectangle(box->X1, box->Y1, box->X2, box->Y2, 0);
				SetPrintColor(PCB->PinColor, 0);
				ALLPAD_LOOP(PCB->Data,
					if ((TEST_FLAG(ONSOLDERFLAG, pad) == 0) == (i == 0))
						Device->PadMask(pad, 0););
				ALLPIN_LOOP(PCB->Data, Device->PinOrViaMask(pin, 0););
				VIA_LOOP(PCB->Data, Device->PinOrViaMask(via, 0););
			}

			if (GlobalOutlineFlag)
				FPrintOutline(1);
			if (GlobalAlignmentFlag)
				FPrintAlignment(1);

			Device->FilledRectangle(box->X1, box->Y1, box->X2, box->Y2, 1);
			SetPrintColor(PCB->PinColor, 1);
			ALLPAD_LOOP(PCB->Data,
				if ((TEST_FLAG(ONSOLDERFLAG, pad) == 0) == (i == 0))
					Device->PadMask(pad, 1););
			ALLPIN_LOOP(PCB->Data, Device->PinOrViaMask(pin, 1););
			VIA_LOOP(PCB->Data, Device->PinOrViaMask(via, 1););

			ClosePrintFile();
		}
	}
	return(0);
}

/* ----------------------------------------------------------------------
 * generates printout
 * mirroring, rotating, scaling and the request for outlines and/or
 * alignment targets were already determined by a dialog
 *
 * output is written either to several files
 * whitespaces have already been removed from 'Command'
 *
 * 'MirrorFlag' has to be negated by the device dependend code if
 * (0,0) is in the lower/left corner for the output device. Some
 * adjustments to the offsets will also be necessary in this case
 *
 * the offsets have to be adjusted if either 'OutlineFlag' or
 * 'AlignmentFlag' is set
 *
 * all offsets are in mil
 */
int Print(char *Command, float Scale,
	Boolean MirrorFlag, Boolean RotateFlag,
	Boolean ColorFlag, Boolean InvertFlag,
	Boolean OutlineFlag, Boolean AlignmentFlag,
	Boolean DrillHelperFlag, Boolean DOSFlag,
	PrintDeviceTypePtr PrintDevice, MediaTypePtr Media,
	Position OffsetX, Position OffsetY,
	Boolean SilkscreenTextFlag)
{
	/* it's not OK to override all files -> user interaction
	 * is required if a file exists
	 */
	ReplaceOK = False;

	/* save pointer... in global identifier */
	Device = PrintDevice;
	GlobalColorFlag = ColorFlag;
	GlobalAlignmentFlag = AlignmentFlag;
	GlobalOutlineFlag = OutlineFlag;
	GlobalDrillHelperFlag = DrillHelperFlag;
	GlobalDOSFlag = DOSFlag;
	GlobalCommand = Command;

	/* set the info struct for the device driver */
	DeviceFlags.SelectedMedia = Media;
	DeviceFlags.MirrorFlag = MirrorFlag;
	DeviceFlags.RotateFlag = RotateFlag;
	DeviceFlags.InvertFlag = InvertFlag;
	DeviceFlags.OffsetX = OffsetX;
	DeviceFlags.OffsetY = OffsetY;
	DeviceFlags.Scale = Scale;

	/* set bounding box coordinates;
	 * the layout has already been checked to be non-empty
	 * selecting outlines or alignment targets causes adjustments
	 */
	if (OutlineFlag) {
		DeviceFlags.BoundingBox.X1 = 0;
		DeviceFlags.BoundingBox.Y1 = 0;
		DeviceFlags.BoundingBox.X2 = PCB->MaxWidth;
		DeviceFlags.BoundingBox.Y2 = PCB->MaxHeight;
	} else
		DeviceFlags.BoundingBox = *GetDataBoundingBox(PCB->Data);

	if (AlignmentFlag) {
		DeviceFlags.BoundingBox.X1 -= Settings.AlignmentDistance;
		DeviceFlags.BoundingBox.Y1 -= Settings.AlignmentDistance;
		DeviceFlags.BoundingBox.X2 += Settings.AlignmentDistance;
		DeviceFlags.BoundingBox.Y2 += Settings.AlignmentDistance;
	}

	/* removes any and all default.map's in a directory */
	/* that a non-gerberX file is being generated. */
	if (Device->NeedsAppMap) {
		unlink("custom.app");
		unlink("default.map");
	}

	Device->init(&DeviceFlags);
	/* OK, call all necessary subroutines, gerberX */
	if (Device->NeedsPrePrint) {
		unlink("gerberX.app");
		if (PrintLayergroups(SilkscreenTextFlag, 1) ||
	                PrintSilkscreen(SilkscreenTextFlag, 1) ||
	                PrintDrill(1) ||
	                PrintMask(1))
			return(1);
		unlink("gerberX.app");
	} else {
		/* OK, call all necessary subroutines */
		if (PrintLayergroups(SilkscreenTextFlag, 0) ||
			PrintSilkscreen(SilkscreenTextFlag, 0) ||
			PrintDrill(0) ||
			PrintMask(0))
			return(1);
	}
	Device->Exit();
	return(0);
}
