/*
 *                            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
 *
 */

/* functions used to undo operations
 *
 * Description:
 * There are two lists which hold
 *   - information about a command
 *   - data of removed objects
 * Both lists are organized as first-in-last-out which means that the undo
 * list can always use the last entry of the remove list.
 * A serial number is incremented whenever an operation is completed.
 * An operation itself may consist of several basic instructions.
 * E.g.: removing all selected objects is one operation with exactly one
 * serial number even if the remove function is called several times.
 *
 * a lock flag ensures that no infinite loops occur
 */

#include "config.h"

#include <assert.h>
#include <memory.h>

#include "buffer.h"
#include "change.h"
#include "create.h"
#include "data.h"
#include "draw.h"
#include "error.h"
#include "insert.h"
#include "layer.h"
#include "misc.h"
#include "mirror.h"
#include "move.h"
#include "polygon.h"
#include "remove.h"
#include "rotate.h"
#include "rtree.h"
#include "search.h"
#include "set.h"
#include "undo.h"
#include "strflags.h"
#include "conf_core.h"
#include "compat_misc.h"
#include "netlist.h"

static pcb_bool between_increment_and_restore = pcb_false;
static pcb_bool added_undo_between_increment_and_restore = pcb_false;

/* ---------------------------------------------------------------------------
 * some local data types
 */
typedef struct {								/* information about a change command */
	char *Name;
} ChangeNameType, *ChangeNameTypePtr;

typedef struct {								/* information about a move command */
	Coord DX, DY;									/* movement vector */
} MoveType, *MoveTypePtr;

typedef struct {								/* information about removed polygon points */
	Coord X, Y;										/* data */
	int ID;
	pcb_cardinal_t Index;								/* index in a polygons array of points */
	pcb_bool last_in_contour;					/* Whether the point was the last in its contour */
} RemovedPointType, *RemovedPointTypePtr;

typedef struct {								/* information about rotation */
	Coord CenterX, CenterY;				/* center of rotation */
	pcb_cardinal_t Steps;								/* number of steps */
} RotateType, *RotateTypePtr;

typedef struct {								/* information about moves between layers */
	pcb_cardinal_t OriginalLayer;				/* the index of the original layer */
} MoveToLayerType, *MoveToLayerTypePtr;

typedef struct {								/* information about layer changes */
	int old_index;
	int new_index;
} LayerChangeType, *LayerChangeTypePtr;

typedef struct {								/* information about poly clear/restore */
	pcb_bool Clear;										/* pcb_true was clear, pcb_false was restore */
	LayerTypePtr Layer;
} ClearPolyType, *ClearPolyTypePtr;

typedef struct {
	Angle angle[2];
} AngleChangeType;

typedef struct {								/* information about netlist lib changes */
	LibraryTypePtr old;
	LibraryTypePtr lib;
} NetlistChangeType, *NetlistChangeTypePtr;

typedef struct {								/* holds information about an operation */
	int Serial,										/* serial number of operation */
	  Type,												/* type of operation */
	  Kind,												/* type of object with given ID */
	  ID;													/* object ID */
	union {												/* some additional information */
		ChangeNameType ChangeName;
		MoveType Move;
		RemovedPointType RemovedPoint;
		RotateType Rotate;
		MoveToLayerType MoveToLayer;
		FlagType Flags;
		Coord Size;
		LayerChangeType LayerChange;
		ClearPolyType ClearPoly;
		NetlistChangeType NetlistChange;
		long int CopyID;
		AngleChangeType AngleChange;
	} Data;
} UndoListType, *UndoListTypePtr;

/* ---------------------------------------------------------------------------
 * some local variables
 */
static DataTypePtr RemoveList = NULL;	/* list of removed objects */
static UndoListTypePtr UndoList = NULL;	/* list of operations */
static int Serial = 1,					/* serial number */
	SavedSerial;
static size_t UndoN, RedoN,			/* number of entries */
  UndoMax;
static pcb_bool Locked = pcb_false;			/* do not add entries if */
static pcb_bool andDraw = pcb_true;
										/* flag is set; prevents from */
										/* infinite loops */

/* ---------------------------------------------------------------------------
 * some local prototypes
 */
static UndoListTypePtr GetUndoSlot(int, int, int);
static void DrawRecoveredObject(int, void *, void *, void *);
static pcb_bool UndoRotate(UndoListTypePtr);
static pcb_bool UndoChangeName(UndoListTypePtr);
static pcb_bool UndoCopyOrCreate(UndoListTypePtr);
static pcb_bool UndoMove(UndoListTypePtr);
static pcb_bool UndoRemove(UndoListTypePtr);
static pcb_bool UndoRemovePoint(UndoListTypePtr);
static pcb_bool UndoInsertPoint(UndoListTypePtr);
static pcb_bool UndoRemoveContour(UndoListTypePtr);
static pcb_bool UndoInsertContour(UndoListTypePtr);
static pcb_bool UndoMoveToLayer(UndoListTypePtr);
static pcb_bool UndoFlag(UndoListTypePtr);
static pcb_bool UndoMirror(UndoListTypePtr);
static pcb_bool UndoChangeSize(UndoListTypePtr);
static pcb_bool UndoChange2ndSize(UndoListTypePtr);
static pcb_bool UndoChangeAngles(UndoListTypePtr);
static pcb_bool UndoChangeRadii(UndoListTypePtr);
static pcb_bool UndoChangeClearSize(UndoListTypePtr);
static pcb_bool UndoChangeMaskSize(UndoListTypePtr);
static pcb_bool UndoClearPoly(UndoListTypePtr);
static int PerformUndo(UndoListTypePtr);

/* ---------------------------------------------------------------------------
 * adds a command plus some data to the undo list
 */
static UndoListTypePtr GetUndoSlot(int CommandType, int ID, int Kind)
{
	UndoListTypePtr ptr;
	void *ptr1, *ptr2, *ptr3;
	int type;
	size_t limit = ((size_t)conf_core.editor.undo_warning_size) * 1024;

#ifdef DEBUG_ID
	if (SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, ID, Kind) == PCB_TYPE_NONE)
		Message(PCB_MSG_DEFAULT, "hace: ID (%d) and Type (%x) mismatch in AddObject...\n", ID, Kind);
#endif

	/* allocate memory */
	if (UndoN >= UndoMax) {
		size_t size;

		UndoMax += STEP_UNDOLIST;
		size = UndoMax * sizeof(UndoListType);
		UndoList = (UndoListTypePtr) realloc(UndoList, size);
		memset(&UndoList[UndoN], 0, STEP_REMOVELIST * sizeof(UndoListType));

		/* ask user to flush the table because of it's size */
		if (size > limit) {
			size_t l2;
			l2 = (size / limit + 1) * limit;
			Message(PCB_MSG_DEFAULT, _("Size of 'undo-list' exceeds %li kb\n"), (long) (l2 >> 10));
		}
	}

	/* free structures from the pruned redo list */

	for (ptr = &UndoList[UndoN]; RedoN; ptr++, RedoN--)
		switch (ptr->Type) {
		case UNDO_CHANGENAME:
		case UNDO_CHANGEPINNUM:
			free(ptr->Data.ChangeName.Name);
			break;
		case UNDO_REMOVE:
			type = SearchObjectByID(RemoveList, &ptr1, &ptr2, &ptr3, ptr->ID, ptr->Kind);
			if (type != PCB_TYPE_NONE) {
				DestroyObject(RemoveList, type, ptr1, ptr2, ptr3);
			}
			break;
		default:
			break;
		}

	if (between_increment_and_restore)
		added_undo_between_increment_and_restore = pcb_true;

	/* copy typefield and serial number to the list */
	ptr = &UndoList[UndoN++];
	ptr->Type = CommandType;
	ptr->Kind = Kind;
	ptr->ID = ID;
	ptr->Serial = Serial;
	return (ptr);
}

/* ---------------------------------------------------------------------------
 * redraws the recovered object
 */
static void DrawRecoveredObject(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	if (Type & (PCB_TYPE_LINE | PCB_TYPE_TEXT | PCB_TYPE_POLYGON | PCB_TYPE_ARC)) {
		LayerTypePtr layer;

		layer = LAYER_PTR(GetLayerNumber(RemoveList, (LayerTypePtr) Ptr1));
		DrawObject(Type, (void *) layer, Ptr2);
	}
	else
		DrawObject(Type, Ptr1, Ptr2);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a 'rotate' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoRotate(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		RotateObject(type, ptr1, ptr2, ptr3,
								 Entry->Data.Rotate.CenterX, Entry->Data.Rotate.CenterY, (4 - Entry->Data.Rotate.Steps) & 0x03);
		Entry->Data.Rotate.Steps = (4 - Entry->Data.Rotate.Steps) & 0x03;
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a clear/restore poly operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoClearPoly(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		if (Entry->Data.ClearPoly.Clear)
			RestoreToPolygon(PCB->Data, type, Entry->Data.ClearPoly.Layer, ptr3);
		else
			ClearFromPolygon(PCB->Data, type, Entry->Data.ClearPoly.Layer, ptr3);
		Entry->Data.ClearPoly.Clear = !Entry->Data.ClearPoly.Clear;
		return pcb_true;
	}
	return pcb_false;
}

/* ---------------------------------------------------------------------------
 * recovers an object from a 'change name' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoChangeName(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		Entry->Data.ChangeName.Name = (char *) (ChangeObjectName(type, ptr1, ptr2, ptr3, Entry->Data.ChangeName.Name));
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a 'change oinnum' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoChangePinnum(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		Entry->Data.ChangeName.Name = (char *) (ChangeObjectPinnum(type, ptr1, ptr2, ptr3, Entry->Data.ChangeName.Name));
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a 2ndSize change operation
 */
static pcb_bool UndoChange2ndSize(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;
	Coord swap;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		swap = ((PinTypePtr) ptr2)->DrillingHole;
		if (andDraw)
			EraseObject(type, ptr1, ptr2);
		((PinTypePtr) ptr2)->DrillingHole = Entry->Data.Size;
		Entry->Data.Size = swap;
		DrawObject(type, ptr1, ptr2);
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a ChangeAngles change operation
 */
static pcb_bool UndoChangeAngles(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;
	double old_sa, old_da;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type == PCB_TYPE_ARC) {
		LayerTypePtr Layer = (LayerTypePtr) ptr1;
		ArcTypePtr a = (ArcTypePtr) ptr2;
		r_delete_entry(Layer->arc_tree, (BoxTypePtr) a);
		old_sa = a->StartAngle;
		old_da = a->Delta;
		if (andDraw)
			EraseObject(type, Layer, a);
		a->StartAngle = Entry->Data.AngleChange.angle[0];
		a->Delta = Entry->Data.AngleChange.angle[1];
		SetArcBoundingBox(a);
		r_insert_entry(Layer->arc_tree, (BoxTypePtr) a, 0);
		Entry->Data.AngleChange.angle[0] = old_sa;
		Entry->Data.AngleChange.angle[1] = old_da;
		DrawObject(type, ptr1, a);
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a ChangeRadii change operation
 */
static pcb_bool UndoChangeRadii(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;
	Coord old_w, old_h;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type == PCB_TYPE_ARC) {
		LayerTypePtr Layer = (LayerTypePtr) ptr1;
		ArcTypePtr a = (ArcTypePtr) ptr2;
		r_delete_entry(Layer->arc_tree, (BoxTypePtr) a);
		old_w = a->Width;
		old_h = a->Height;
		if (andDraw)
			EraseObject(type, Layer, a);
		a->Width = Entry->Data.Move.DX;
		a->Height = Entry->Data.Move.DY;
		SetArcBoundingBox(a);
		r_insert_entry(Layer->arc_tree, (BoxTypePtr) a, 0);
		Entry->Data.Move.DX = old_w;
		Entry->Data.Move.DY = old_h;
		DrawObject(type, ptr1, a);
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a clearance size change operation
 */
static pcb_bool UndoChangeClearSize(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;
	Coord swap;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		swap = ((PinTypePtr) ptr2)->Clearance;
		RestoreToPolygon(PCB->Data, type, ptr1, ptr2);
		if (andDraw)
			EraseObject(type, ptr1, ptr2);
		((PinTypePtr) ptr2)->Clearance = Entry->Data.Size;
		ClearFromPolygon(PCB->Data, type, ptr1, ptr2);
		Entry->Data.Size = swap;
		if (andDraw)
			DrawObject(type, ptr1, ptr2);
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a mask size change operation
 */
static pcb_bool UndoChangeMaskSize(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;
	Coord swap;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type & (PCB_TYPE_VIA | PCB_TYPE_PIN | PCB_TYPE_PAD)) {
		swap = (type == PCB_TYPE_PAD ? ((PadTypePtr) ptr2)->Mask : ((PinTypePtr) ptr2)->Mask);
		if (andDraw)
			EraseObject(type, ptr1, ptr2);
		if (type == PCB_TYPE_PAD)
			((PadTypePtr) ptr2)->Mask = Entry->Data.Size;
		else
			((PinTypePtr) ptr2)->Mask = Entry->Data.Size;
		Entry->Data.Size = swap;
		if (andDraw)
			DrawObject(type, ptr1, ptr2);
		return (pcb_true);
	}
	return (pcb_false);
}


/* ---------------------------------------------------------------------------
 * recovers an object from a Size change operation
 */
static pcb_bool UndoChangeSize(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3, *ptr1e;
	int type;
	Coord swap;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
		if (type == PCB_TYPE_ELEMENT_NAME)
			ptr1e = NULL;
		else
			ptr1e = ptr1;

	if (type != PCB_TYPE_NONE) {
		/* Wow! can any object be treated as a pin type for size change?? */
		/* pins, vias, lines, and arcs can. Text can't but it has it's own mechanism */
		swap = ((PinTypePtr) ptr2)->Thickness;
		RestoreToPolygon(PCB->Data, type, ptr1, ptr2);
		if ((andDraw) && (ptr1e != NULL))
			EraseObject(type, ptr1e, ptr2);
		((PinTypePtr) ptr2)->Thickness = Entry->Data.Size;
		Entry->Data.Size = swap;
		ClearFromPolygon(PCB->Data, type, ptr1, ptr2);
		if (andDraw)
			DrawObject(type, ptr1, ptr2);
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a FLAG change operation
 */
static pcb_bool UndoFlag(UndoListTypePtr Entry)
{
	void *ptr1, *ptr1e, *ptr2, *ptr3;
	int type;
	FlagType swap;
	int must_redraw;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		FlagType f1, f2;
		PinTypePtr pin = (PinTypePtr) ptr2;

		if ((type == PCB_TYPE_ELEMENT) || (type == PCB_TYPE_ELEMENT_NAME))
			ptr1e = NULL;
		else
			ptr1e = ptr1;

		swap = pin->Flags;

		must_redraw = 0;
		f1 = MaskFlags(pin->Flags, ~DRAW_FLAGS);
		f2 = MaskFlags(Entry->Data.Flags, ~DRAW_FLAGS);

		if (!FLAGS_EQUAL(f1, f2))
			must_redraw = 1;

		if (andDraw && must_redraw && (ptr1e != NULL))
			EraseObject(type, ptr1e, ptr2);

		pin->Flags = Entry->Data.Flags;

		Entry->Data.Flags = swap;

		if (andDraw && must_redraw)
			DrawObject(type, ptr1, ptr2);
		return (pcb_true);
	}
	Message(PCB_MSG_DEFAULT, "hace Internal error: Can't find ID %d type %08x\n", Entry->ID, Entry->Kind);
	Message(PCB_MSG_DEFAULT, "for UndoFlag Operation. Previous flags: %s\n", flags_to_string(Entry->Data.Flags, 0));
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a mirror operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoMirror(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type == PCB_TYPE_ELEMENT) {
		ElementTypePtr element = (ElementTypePtr) ptr3;
		if (andDraw)
			EraseElement(element);
		MirrorElementCoordinates(PCB->Data, element, Entry->Data.Move.DY);
		if (andDraw)
			DrawElement(element);
		return (pcb_true);
	}
	Message(PCB_MSG_DEFAULT, "hace Internal error: UndoMirror on object type %d\n", type);
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a 'copy' or 'create' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoCopyOrCreate(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		if (!RemoveList)
			RemoveList = CreateNewBuffer();
		if (andDraw)
			EraseObject(type, ptr1, ptr2);
		/* in order to make this re-doable we move it to the RemoveList */
		MoveObjectToBuffer(RemoveList, PCB->Data, type, ptr1, ptr2, ptr3);
		Entry->Type = UNDO_REMOVE;
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers an object from a 'move' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoMove(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		MoveObject(type, ptr1, ptr2, ptr3, -Entry->Data.Move.DX, -Entry->Data.Move.DY);
		Entry->Data.Move.DX *= -1;
		Entry->Data.Move.DY *= -1;
		return (pcb_true);
	}
	return (pcb_false);
}

/* ----------------------------------------------------------------------
 * recovers an object from a 'remove' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoRemove(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type;

	/* lookup entry by it's ID */
	type = SearchObjectByID(RemoveList, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		if (andDraw)
			DrawRecoveredObject(type, ptr1, ptr2, ptr3);
		MoveObjectToBuffer(PCB->Data, RemoveList, type, ptr1, ptr2, ptr3);
		Entry->Type = UNDO_CREATE;
		return (pcb_true);
	}
	return (pcb_false);
}

/* ----------------------------------------------------------------------
 * recovers an object from a 'move to another layer' operation
 * returns pcb_true if anything has been recovered
 */
static pcb_bool UndoMoveToLayer(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	int type, swap;

	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, &ptr1, &ptr2, &ptr3, Entry->ID, Entry->Kind);
	if (type != PCB_TYPE_NONE) {
		swap = GetLayerNumber(PCB->Data, (LayerTypePtr) ptr1);
		MoveObjectToLayer(type, ptr1, ptr2, ptr3, LAYER_PTR(Entry->Data.MoveToLayer.OriginalLayer), pcb_true);
		Entry->Data.MoveToLayer.OriginalLayer = swap;
		return (pcb_true);
	}
	return (pcb_false);
}

/* ---------------------------------------------------------------------------
 * recovers a removed polygon point
 * returns pcb_true on success
 */
static pcb_bool UndoRemovePoint(UndoListTypePtr Entry)
{
	LayerTypePtr layer;
	PolygonTypePtr polygon;
	void *ptr3;
	int type;

	/* lookup entry (polygon not point was saved) by it's ID */
	assert(Entry->Kind == PCB_TYPE_POLYGON);
	type = SearchObjectByID(PCB->Data, (void **) &layer, (void **) &polygon, &ptr3, Entry->ID, Entry->Kind);
	switch (type) {
	case PCB_TYPE_POLYGON:						/* restore the removed point */
		{
			/* recover the point */
			if (andDraw && layer->On)
				ErasePolygon(polygon);
			InsertPointIntoObject(PCB_TYPE_POLYGON, layer, polygon,
														&Entry->Data.RemovedPoint.Index,
														Entry->Data.RemovedPoint.X,
														Entry->Data.RemovedPoint.Y, pcb_true, Entry->Data.RemovedPoint.last_in_contour);

			polygon->Points[Entry->Data.RemovedPoint.Index].ID = Entry->Data.RemovedPoint.ID;
			if (andDraw && layer->On)
				DrawPolygon(layer, polygon);
			Entry->Type = UNDO_INSERT_POINT;
			Entry->ID = Entry->Data.RemovedPoint.ID;
			Entry->Kind = PCB_TYPE_POLYGON_POINT;
			return (pcb_true);
		}

	default:
		return (pcb_false);
	}
}

/* ---------------------------------------------------------------------------
 * recovers an inserted polygon point
 * returns pcb_true on success
 */
static pcb_bool UndoInsertPoint(UndoListTypePtr Entry)
{
	LayerTypePtr layer;
	PolygonTypePtr polygon;
	PointTypePtr pnt;
	int type;
	pcb_cardinal_t point_idx;
	pcb_cardinal_t hole;
	pcb_bool last_in_contour = pcb_false;

	assert(Entry->Kind == PCB_TYPE_POLYGON_POINT);
	/* lookup entry by it's ID */
	type = SearchObjectByID(PCB->Data, (void **) &layer, (void **) &polygon, (void **) &pnt, Entry->ID, Entry->Kind);
	switch (type) {
	case PCB_TYPE_POLYGON_POINT:			/* removes an inserted polygon point */
		{
			if (andDraw && layer->On)
				ErasePolygon(polygon);

			/* Check whether this point was at the end of its contour.
			 * If so, we need to flag as such when re-adding the point
			 * so it goes back in the correct place
			 */
			point_idx = polygon_point_idx(polygon, pnt);
			for (hole = 0; hole < polygon->HoleIndexN; hole++)
				if (point_idx == polygon->HoleIndex[hole] - 1)
					last_in_contour = pcb_true;
			if (point_idx == polygon->PointN - 1)
				last_in_contour = pcb_true;
			Entry->Data.RemovedPoint.last_in_contour = last_in_contour;

			Entry->Data.RemovedPoint.X = pnt->X;
			Entry->Data.RemovedPoint.Y = pnt->Y;
			Entry->Data.RemovedPoint.ID = pnt->ID;
			Entry->ID = polygon->ID;
			Entry->Kind = PCB_TYPE_POLYGON;
			Entry->Type = UNDO_REMOVE_POINT;
			Entry->Data.RemovedPoint.Index = point_idx;
			DestroyObject(PCB->Data, PCB_TYPE_POLYGON_POINT, layer, polygon, pnt);
			if (andDraw && layer->On)
				DrawPolygon(layer, polygon);
			return (pcb_true);
		}

	default:
		return (pcb_false);
	}
}

static pcb_bool UndoSwapCopiedObject(UndoListTypePtr Entry)
{
	void *ptr1, *ptr2, *ptr3;
	void *ptr1b, *ptr2b, *ptr3b;
	AnyObjectType *obj, *obj2;
	int type;
	long int swap_id;

	/* lookup entry by it's ID */
	type = SearchObjectByID(RemoveList, &ptr1, &ptr2, &ptr3, Entry->Data.CopyID, Entry->Kind);
	if (type == PCB_TYPE_NONE)
		return pcb_false;

	type = SearchObjectByID(PCB->Data, &ptr1b, &ptr2b, &ptr3b, Entry->ID, Entry->Kind);
	if (type == PCB_TYPE_NONE)
		return FALSE;

	obj = (AnyObjectType *) ptr2;
	obj2 = (AnyObjectType *) ptr2b;

	swap_id = obj->ID;
	obj->ID = obj2->ID;
	obj2->ID = swap_id;

	MoveObjectToBuffer(RemoveList, PCB->Data, type, ptr1b, ptr2b, ptr3b);

	if (andDraw)
		DrawRecoveredObject(Entry->Kind, ptr1, ptr2, ptr3);

	obj = (AnyObjectType *) MoveObjectToBuffer(PCB->Data, RemoveList, type, ptr1, ptr2, ptr3);
	if (Entry->Kind == PCB_TYPE_POLYGON)
		InitClip(PCB->Data, (LayerTypePtr) ptr1b, (PolygonType *) obj);
	return (pcb_true);
}

/* ---------------------------------------------------------------------------
 * recovers an removed polygon point
 * returns pcb_true on success
 */
static pcb_bool UndoRemoveContour(UndoListTypePtr Entry)
{
	assert(Entry->Kind == PCB_TYPE_POLYGON);
	return UndoSwapCopiedObject(Entry);
}

/* ---------------------------------------------------------------------------
 * recovers an inserted polygon point
 * returns pcb_true on success
 */
static pcb_bool UndoInsertContour(UndoListTypePtr Entry)
{
	assert(Entry->Kind == PCB_TYPE_POLYGON);
	return UndoSwapCopiedObject(Entry);
}

/* ---------------------------------------------------------------------------
 * undo a layer change
 * returns pcb_true on success
 */
static pcb_bool UndoLayerChange(UndoListTypePtr Entry)
{
	LayerChangeTypePtr l = &Entry->Data.LayerChange;
	int tmp;

	tmp = l->new_index;
	l->new_index = l->old_index;
	l->old_index = tmp;

	if (MoveLayer(l->old_index, l->new_index))
		return pcb_false;
	else
		return pcb_true;
}

/* ---------------------------------------------------------------------------
 * undo a netlist change
 * returns pcb_true on success
 */
static pcb_bool UndoNetlistChange(UndoListTypePtr Entry)
{
	NetlistChangeTypePtr l = &Entry->Data.NetlistChange;
	unsigned int i, j;
	LibraryTypePtr lib, saved;

	lib = l->lib;
	saved = l->old;

	/* iterate over each net */
	for (i = 0; i < lib->MenuN; i++) {
		free(lib->Menu[i].Name);
		free(lib->Menu[i].directory);
		free(lib->Menu[i].Style);

		/* iterate over each pin on the net */
		for (j = 0; j < lib->Menu[i].EntryN; j++) {
			if (!lib->Menu[i].Entry[j].ListEntry_dontfree)
				free((char*)lib->Menu[i].Entry[j].ListEntry);

			free((char*)lib->Menu[i].Entry[j].Package);
			free((char*)lib->Menu[i].Entry[j].Value);
			free((char*)lib->Menu[i].Entry[j].Description);
		}
	}

	free(lib->Menu);

	*lib = *saved;

	pcb_netlist_changed(0);
	return pcb_true;
}

/* ---------------------------------------------------------------------------
 * undo of any 'hard to recover' operation
 *
 * returns the bitfield for the types of operations that were undone
 */
int Undo(pcb_bool draw)
{
	UndoListTypePtr ptr;
	int Types = 0;
	int unique;
	pcb_bool error_undoing = pcb_false;

	unique = conf_core.editor.unique_names;
	conf_force_set_bool(conf_core.editor.unique_names, 0);

	andDraw = draw;

	if (Serial == 0) {
		Message(PCB_MSG_DEFAULT, _("ERROR: Attempt to Undo() with Serial == 0\n" "       Please save your work and report this bug.\n"));
		return 0;
	}

	if (UndoN == 0) {
		Message(PCB_MSG_DEFAULT, _("Nothing to undo - buffer is empty\n"));
		return 0;
	}

	Serial--;

	ptr = &UndoList[UndoN - 1];

	if (ptr->Serial > Serial) {
		Message(PCB_MSG_DEFAULT, _("ERROR: Bad undo serial number %d in undo stack - expecting %d or lower\n"
							"       Please save your work and report this bug.\n"), ptr->Serial, Serial);

	/* It is likely that the serial number got corrupted through some bad
		 * use of the SaveUndoSerialNumber() / RestoreUndoSerialNumber() APIs.
		 *
		 * Reset the serial number to be consistent with that of the last
		 * operation on the undo stack in the hope that this might clear
		 * the problem and  allow the user to hit Undo again.
		 */
		Serial = ptr->Serial + 1;
		return 0;
	}

	LockUndo();										/* lock undo module to prevent from loops */

	/* Loop over all entries with the correct serial number */
	for (; UndoN && ptr->Serial == Serial; ptr--, UndoN--, RedoN++) {
		int undid = PerformUndo(ptr);
		if (undid == 0)
			error_undoing = pcb_true;
		Types |= undid;
	}

	UnlockUndo();

	if (error_undoing)
		Message(PCB_MSG_DEFAULT, _("ERROR: Failed to undo some operations\n"));

	if (Types && andDraw)
		Draw();

	/* restore the unique flag setting */
	conf_force_set_bool(conf_core.editor.unique_names, unique);

	return Types;
}

static int PerformUndo(UndoListTypePtr ptr)
{
	switch (ptr->Type) {
	case UNDO_CHANGENAME:
		if (UndoChangeName(ptr))
			return (UNDO_CHANGENAME);
		break;

	case UNDO_CHANGEPINNUM:
		if (UndoChangePinnum(ptr))
			return (UNDO_CHANGEPINNUM);
		break;

	case UNDO_CREATE:
		if (UndoCopyOrCreate(ptr))
			return (UNDO_CREATE);
		break;

	case UNDO_MOVE:
		if (UndoMove(ptr))
			return (UNDO_MOVE);
		break;

	case UNDO_REMOVE:
		if (UndoRemove(ptr))
			return (UNDO_REMOVE);
		break;

	case UNDO_REMOVE_POINT:
		if (UndoRemovePoint(ptr))
			return (UNDO_REMOVE_POINT);
		break;

	case UNDO_INSERT_POINT:
		if (UndoInsertPoint(ptr))
			return (UNDO_INSERT_POINT);
		break;

	case UNDO_REMOVE_CONTOUR:
		if (UndoRemoveContour(ptr))
			return (UNDO_REMOVE_CONTOUR);
		break;

	case UNDO_INSERT_CONTOUR:
		if (UndoInsertContour(ptr))
			return (UNDO_INSERT_CONTOUR);
		break;

	case UNDO_ROTATE:
		if (UndoRotate(ptr))
			return (UNDO_ROTATE);
		break;

	case UNDO_CLEAR:
		if (UndoClearPoly(ptr))
			return (UNDO_CLEAR);
		break;

	case UNDO_MOVETOLAYER:
		if (UndoMoveToLayer(ptr))
			return (UNDO_MOVETOLAYER);
		break;

	case UNDO_FLAG:
		if (UndoFlag(ptr))
			return (UNDO_FLAG);
		break;

	case UNDO_CHANGESIZE:
		if (UndoChangeSize(ptr))
			return (UNDO_CHANGESIZE);
		break;

	case UNDO_CHANGECLEARSIZE:
		if (UndoChangeClearSize(ptr))
			return (UNDO_CHANGECLEARSIZE);
		break;

	case UNDO_CHANGEMASKSIZE:
		if (UndoChangeMaskSize(ptr))
			return (UNDO_CHANGEMASKSIZE);
		break;

	case UNDO_CHANGE2NDSIZE:
		if (UndoChange2ndSize(ptr))
			return (UNDO_CHANGE2NDSIZE);
		break;

	case UNDO_CHANGEANGLES:
		if (UndoChangeAngles(ptr))
			return (UNDO_CHANGEANGLES);
		break;

	case UNDO_CHANGERADII:
		if (UndoChangeRadii(ptr))
			return (UNDO_CHANGERADII);
		break;

	case UNDO_LAYERCHANGE:
		if (UndoLayerChange(ptr))
			return (UNDO_LAYERCHANGE);
		break;

	case UNDO_NETLISTCHANGE:
		if (UndoNetlistChange(ptr))
			return (UNDO_NETLISTCHANGE);
		break;

	case UNDO_MIRROR:
		if (UndoMirror(ptr))
			return (UNDO_MIRROR);
		break;
	}
	return 0;
}

/* ---------------------------------------------------------------------------
 * redo of any 'hard to recover' operation
 *
 * returns the number of operations redone
 */
int Redo(pcb_bool draw)
{
	UndoListTypePtr ptr;
	int Types = 0;
	pcb_bool error_undoing = pcb_false;

	andDraw = draw;

	if (RedoN == 0) {
		Message(PCB_MSG_DEFAULT, _("Nothing to redo. Perhaps changes have been made since last undo\n"));
		return 0;
	}

	ptr = &UndoList[UndoN];

	if (ptr->Serial < Serial) {
		Message(PCB_MSG_DEFAULT, _("ERROR: Bad undo serial number %d in redo stack - expecting %d or higher\n"
							"       Please save your work and report this bug.\n"), ptr->Serial, Serial);

		/* It is likely that the serial number got corrupted through some bad
		 * use of the SaveUndoSerialNumber() / RestoreUndoSerialNumber() APIs.
		 *
		 * Reset the serial number to be consistent with that of the first
		 * operation on the redo stack in the hope that this might clear
		 * the problem and  allow the user to hit Redo again.
		 */
		Serial = ptr->Serial;
		return 0;
	}

	LockUndo();										/* lock undo module to prevent from loops */

	/* and loop over all entries with the correct serial number */
	for (; RedoN && ptr->Serial == Serial; ptr++, UndoN++, RedoN--) {
		int undid = PerformUndo(ptr);
		if (undid == 0)
			error_undoing = pcb_true;
		Types |= undid;
	}

	/* Make next serial number current */
	Serial++;

	UnlockUndo();

	if (error_undoing)
		Message(PCB_MSG_DEFAULT, _("ERROR: Failed to redo some operations\n"));

	if (Types && andDraw)
		Draw();

	return Types;
}

/* ---------------------------------------------------------------------------
 * restores the serial number of the undo list
 */
void RestoreUndoSerialNumber(void)
{
	if (added_undo_between_increment_and_restore)
		Message(PCB_MSG_DEFAULT, _("ERROR: Operations were added to the Undo stack with an incorrect serial number\n"));
	between_increment_and_restore = pcb_false;
	added_undo_between_increment_and_restore = pcb_false;
	Serial = SavedSerial;
}

/* ---------------------------------------------------------------------------
 * saves the serial number of the undo list
 */
void SaveUndoSerialNumber(void)
{
	Bumped = pcb_false;
	between_increment_and_restore = pcb_false;
	added_undo_between_increment_and_restore = pcb_false;
	SavedSerial = Serial;
}

/* ---------------------------------------------------------------------------
 * increments the serial number of the undo list
 * it's not done automatically because some operations perform more
 * than one request with the same serial #
 */
void IncrementUndoSerialNumber(void)
{
	if (!Locked) {
		/* Set the changed flag if anything was added prior to this bump */
		if (UndoN > 0 && UndoList[UndoN - 1].Serial == Serial)
			SetChangedFlag(pcb_true);
		Serial++;
		Bumped = pcb_true;
		between_increment_and_restore = pcb_true;
	}
}

/* ---------------------------------------------------------------------------
 * releases memory of the undo- and remove list
 */
void ClearUndoList(pcb_bool Force)
{
	UndoListTypePtr undo;

	if (UndoN && (Force || gui->confirm_dialog("OK to clear 'undo' buffer?", 0))) {
		/* release memory allocated by objects in undo list */
		for (undo = UndoList; UndoN; undo++, UndoN--) {
			if ((undo->Type == UNDO_CHANGENAME) || (undo->Type == UNDO_CHANGEPINNUM))
				free(undo->Data.ChangeName.Name);
		}
		free(UndoList);
		UndoList = NULL;
		if (RemoveList) {
			FreeDataMemory(RemoveList);
			free(RemoveList);
			RemoveList = NULL;
		}

		/* reset some counters */
		UndoN = UndoMax = RedoN = 0;
	}

	/* reset counter in any case */
	Serial = 1;
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of clearpoly objects
 */
void AddObjectToClearPolyUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3, pcb_bool clear)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CLEAR, OBJECT_ID(Ptr3), Type);
		undo->Data.ClearPoly.Clear = clear;
		undo->Data.ClearPoly.Layer = (LayerTypePtr) Ptr1;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of mirrored objects
 */
void AddObjectToMirrorUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3, Coord yoff)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_MIRROR, OBJECT_ID(Ptr3), Type);
		undo->Data.Move.DY = yoff;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of rotated objects
 */
void AddObjectToRotateUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3, Coord CenterX, Coord CenterY, pcb_uint8_t Steps)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_ROTATE, OBJECT_ID(Ptr3), Type);
		undo->Data.Rotate.CenterX = CenterX;
		undo->Data.Rotate.CenterY = CenterY;
		undo->Data.Rotate.Steps = Steps;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of removed objects and removes it from
 * the current PCB
 */
void MoveObjectToRemoveUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	if (Locked)
		return;

	if (!RemoveList)
		RemoveList = CreateNewBuffer();

	GetUndoSlot(UNDO_REMOVE, OBJECT_ID(Ptr3), Type);
	MoveObjectToBuffer(RemoveList, PCB->Data, Type, Ptr1, Ptr2, Ptr3);
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of removed polygon/... points
 */
void AddObjectToRemovePointUndoList(int Type, void *Ptr1, void *Ptr2, pcb_cardinal_t index)
{
	UndoListTypePtr undo;
	PolygonTypePtr polygon = (PolygonTypePtr) Ptr2;
	pcb_cardinal_t hole;
	pcb_bool last_in_contour = pcb_false;

	if (!Locked) {
		switch (Type) {
		case PCB_TYPE_POLYGON_POINT:
			{
				/* save the ID of the parent object; else it will be
				 * impossible to recover the point
				 */
				undo = GetUndoSlot(UNDO_REMOVE_POINT, OBJECT_ID(polygon), PCB_TYPE_POLYGON);
				undo->Data.RemovedPoint.X = polygon->Points[index].X;
				undo->Data.RemovedPoint.Y = polygon->Points[index].Y;
				undo->Data.RemovedPoint.ID = polygon->Points[index].ID;
				undo->Data.RemovedPoint.Index = index;

				/* Check whether this point was at the end of its contour.
				 * If so, we need to flag as such when re-adding the point
				 * so it goes back in the correct place
				 */
				for (hole = 0; hole < polygon->HoleIndexN; hole++)
					if (index == polygon->HoleIndex[hole] - 1)
						last_in_contour = pcb_true;
				if (index == polygon->PointN - 1)
					last_in_contour = pcb_true;
				undo->Data.RemovedPoint.last_in_contour = last_in_contour;
			}
			break;
		}
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of inserted polygon/... points
 */
void AddObjectToInsertPointUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	if (!Locked)
		GetUndoSlot(UNDO_INSERT_POINT, OBJECT_ID(Ptr3), Type);
}

static void CopyObjectToUndoList(int undo_type, int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	UndoListTypePtr undo;
	AnyObjectType *copy;

	if (Locked)
		return;

	if (!RemoveList)
		RemoveList = CreateNewBuffer();

	undo = GetUndoSlot(undo_type, OBJECT_ID(Ptr2), Type);
	copy = (AnyObjectType *) CopyObjectToBuffer(RemoveList, PCB->Data, Type, Ptr1, Ptr2, Ptr3);
	undo->Data.CopyID = copy->ID;
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of removed contours
 * (Actually just takes a copy of the whole polygon to restore)
 */
void AddObjectToRemoveContourUndoList(int Type, LayerType * Layer, PolygonType * Polygon)
{
	CopyObjectToUndoList(UNDO_REMOVE_CONTOUR, Type, Layer, Polygon, NULL);
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of insert contours
 * (Actually just takes a copy of the whole polygon to restore)
 */
void AddObjectToInsertContourUndoList(int Type, LayerType * Layer, PolygonType * Polygon)
{
	CopyObjectToUndoList(UNDO_INSERT_CONTOUR, Type, Layer, Polygon, NULL);
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of moved objects
 */
void AddObjectToMoveUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3, Coord DX, Coord DY)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_MOVE, OBJECT_ID(Ptr3), Type);
		undo->Data.Move.DX = DX;
		undo->Data.Move.DY = DY;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with changed names
 */
void AddObjectToChangeNameUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3, char *OldName)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGENAME, OBJECT_ID(Ptr3), Type);
		undo->Data.ChangeName.Name = OldName;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with changed pinnums
 */
void AddObjectToChangePinnumUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3, char *OldName)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGEPINNUM, OBJECT_ID(Ptr3), Type);
		undo->Data.ChangeName.Name = OldName;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects moved to another layer
 */
void AddObjectToMoveToLayerUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_MOVETOLAYER, OBJECT_ID(Ptr3), Type);
		undo->Data.MoveToLayer.OriginalLayer = GetLayerNumber(PCB->Data, (LayerTypePtr) Ptr1);
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of created objects
 */
void AddObjectToCreateUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	if (!Locked)
		GetUndoSlot(UNDO_CREATE, OBJECT_ID(Ptr3), Type);
	ClearFromPolygon(PCB->Data, Type, Ptr1, Ptr2);
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with flags changed
 */
void AddObjectToFlagUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_FLAG, OBJECT_ID(Ptr2), Type);
		undo->Data.Flags = ((PinTypePtr) Ptr2)->Flags;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with Size changes
 */
void AddObjectToSizeUndoList(int Type, void *ptr1, void *ptr2, void *ptr3)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGESIZE, OBJECT_ID(ptr2), Type);
		switch (Type) {
		case PCB_TYPE_PIN:
		case PCB_TYPE_VIA:
			undo->Data.Size = ((PinTypePtr) ptr2)->Thickness;
			break;
		case PCB_TYPE_LINE:
		case PCB_TYPE_ELEMENT_LINE:
			undo->Data.Size = ((LineTypePtr) ptr2)->Thickness;
			break;
		case PCB_TYPE_TEXT:
		case PCB_TYPE_ELEMENT_NAME:
			undo->Data.Size = ((TextTypePtr) ptr2)->Scale;
			break;
		case PCB_TYPE_PAD:
			undo->Data.Size = ((PadTypePtr) ptr2)->Thickness;
			break;
		case PCB_TYPE_ARC:
		case PCB_TYPE_ELEMENT_ARC:
			undo->Data.Size = ((ArcTypePtr) ptr2)->Thickness;
			break;
		}
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with Size changes
 */
void AddObjectToClearSizeUndoList(int Type, void *ptr1, void *ptr2, void *ptr3)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGECLEARSIZE, OBJECT_ID(ptr2), Type);
		switch (Type) {
		case PCB_TYPE_PIN:
		case PCB_TYPE_VIA:
			undo->Data.Size = ((PinTypePtr) ptr2)->Clearance;
			break;
		case PCB_TYPE_LINE:
			undo->Data.Size = ((LineTypePtr) ptr2)->Clearance;
			break;
		case PCB_TYPE_PAD:
			undo->Data.Size = ((PadTypePtr) ptr2)->Clearance;
			break;
		case PCB_TYPE_ARC:
			undo->Data.Size = ((ArcTypePtr) ptr2)->Clearance;
			break;
		}
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with Size changes
 */
void AddObjectToMaskSizeUndoList(int Type, void *ptr1, void *ptr2, void *ptr3)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGEMASKSIZE, OBJECT_ID(ptr2), Type);
		switch (Type) {
		case PCB_TYPE_PIN:
		case PCB_TYPE_VIA:
			undo->Data.Size = ((PinTypePtr) ptr2)->Mask;
			break;
		case PCB_TYPE_PAD:
			undo->Data.Size = ((PadTypePtr) ptr2)->Mask;
			break;
		}
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of objects with 2ndSize changes
 */
void AddObjectTo2ndSizeUndoList(int Type, void *ptr1, void *ptr2, void *ptr3)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGE2NDSIZE, OBJECT_ID(ptr2), Type);
		if (Type == PCB_TYPE_PIN || Type == PCB_TYPE_VIA)
			undo->Data.Size = ((PinTypePtr) ptr2)->DrillingHole;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of changed angles.  Note that you must
 * call this before changing the angles, passing the new start/delta.
 */
void AddObjectToChangeAnglesUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	UndoListTypePtr undo;
	ArcTypePtr a = (ArcTypePtr) Ptr3;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGEANGLES, OBJECT_ID(Ptr3), Type);
		undo->Data.AngleChange.angle[0] = a->StartAngle;
		undo->Data.AngleChange.angle[1] = a->Delta;
	}
}

/* ---------------------------------------------------------------------------
 * adds an object to the list of changed radii.  Note that you must
 * call this before changing the radii, passing the new width/height.
 */
void AddObjectToChangeRadiiUndoList(int Type, void *Ptr1, void *Ptr2, void *Ptr3)
{
	UndoListTypePtr undo;
	ArcTypePtr a = (ArcTypePtr) Ptr3;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_CHANGERADII, OBJECT_ID(Ptr3), Type);
		undo->Data.Move.DX = a->Width;
		undo->Data.Move.DY = a->Height;
	}
}

/* ---------------------------------------------------------------------------
 * adds a layer change (new, delete, move) to the undo list.
 */
void AddLayerChangeToUndoList(int old_index, int new_index)
{
	UndoListTypePtr undo;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_LAYERCHANGE, 0, 0);
		undo->Data.LayerChange.old_index = old_index;
		undo->Data.LayerChange.new_index = new_index;
	}
}

/* ---------------------------------------------------------------------------
 * adds a netlist change to the undo list
 */
void AddNetlistLibToUndoList(LibraryTypePtr lib)
{
	UndoListTypePtr undo;
	unsigned int i, j;
	LibraryTypePtr old;

	if (!Locked) {
		undo = GetUndoSlot(UNDO_NETLISTCHANGE, 0, 0);
		/* keep track of where the data needs to go */
		undo->Data.NetlistChange.lib = lib;

		/* and what the old data is that we'll need to restore */
		undo->Data.NetlistChange.old = (LibraryTypePtr) malloc(sizeof(LibraryTypePtr));
		old = undo->Data.NetlistChange.old;
		old->MenuN = lib->MenuN;
		old->MenuMax = lib->MenuMax;
		old->Menu = (LibraryMenuTypePtr) malloc(old->MenuMax * sizeof(LibraryMenuType));
		if (old->Menu == NULL) {
			fprintf(stderr, "malloc() failed in AddNetlistLibToUndoList\n");
			exit(1);
		}

		/* iterate over each net */
		for (i = 0; i < lib->MenuN; i++) {
			old->Menu[i].EntryN = lib->Menu[i].EntryN;
			old->Menu[i].EntryMax = lib->Menu[i].EntryMax;

			old->Menu[i].Name = lib->Menu[i].Name ? pcb_strdup(lib->Menu[i].Name) : NULL;

			old->Menu[i].directory = lib->Menu[i].directory ? pcb_strdup(lib->Menu[i].directory) : NULL;

			old->Menu[i].Style = lib->Menu[i].Style ? pcb_strdup(lib->Menu[i].Style) : NULL;


			old->Menu[i].Entry = (LibraryEntryTypePtr) malloc(old->Menu[i].EntryMax * sizeof(LibraryEntryType));
			if (old->Menu[i].Entry == NULL) {
				fprintf(stderr, "malloc() failed in AddNetlistLibToUndoList\n");
				exit(1);
			}

			/* iterate over each pin on the net */
			for (j = 0; j < lib->Menu[i].EntryN; j++) {

				old->Menu[i].Entry[j].ListEntry = lib->Menu[i].Entry[j].ListEntry ? pcb_strdup(lib->Menu[i].Entry[j].ListEntry) : NULL;
				old->Menu[i].Entry[j].ListEntry_dontfree = 0;

				old->Menu[i].Entry[j].Package = lib->Menu[i].Entry[j].Package ? pcb_strdup(lib->Menu[i].Entry[j].Package) : NULL;

				old->Menu[i].Entry[j].Value = lib->Menu[i].Entry[j].Value ? pcb_strdup(lib->Menu[i].Entry[j].Value) : NULL;

				old->Menu[i].Entry[j].Description =
					lib->Menu[i].Entry[j].Description ? pcb_strdup(lib->Menu[i].Entry[j].Description) : NULL;


			}
		}

	}
}

/* ---------------------------------------------------------------------------
 * set lock flag
 */
void LockUndo(void)
{
	Locked = pcb_true;
}

/* ---------------------------------------------------------------------------
 * reset lock flag
 */
void UnlockUndo(void)
{
	Locked = pcb_false;
}

/* ---------------------------------------------------------------------------
 * return undo lock state
 */
pcb_bool Undoing(void)
{
	return (Locked);
}

#ifndef NDEBUG
static const char *undo_type2str(int type)
{
	static char buff[32];
	switch(type) {
		case UNDO_CHANGENAME: return "changename";
		case UNDO_MOVE: return "move";
		case UNDO_REMOVE: return "remove";
		case UNDO_REMOVE_POINT: return "remove_point";
		case UNDO_INSERT_POINT: return "insert_point";
		case UNDO_REMOVE_CONTOUR: return "remove_contour";
		case UNDO_INSERT_CONTOUR: return "insert_contour";
		case UNDO_ROTATE: return "rotate";
		case UNDO_CREATE: return "create";
		case UNDO_MOVETOLAYER: return "movetolayer";
		case UNDO_FLAG: return "flag";
		case UNDO_CHANGESIZE: return "changesize";
		case UNDO_CHANGE2NDSIZE: return "change2ndsize";
		case UNDO_MIRROR: return "mirror";
		case UNDO_CHANGECLEARSIZE: return "chngeclearsize";
		case UNDO_CHANGEMASKSIZE: return "changemasksize";
		case UNDO_CHANGEANGLES: return "changeangles";
		case UNDO_CHANGERADII: return "changeradii";
		case UNDO_LAYERCHANGE: return "layerchange";
		case UNDO_CLEAR: return "clear";
		case UNDO_NETLISTCHANGE: return "netlistchange";
		case UNDO_CHANGEPINNUM: return "changepinnum";
	}
	sprintf(buff, "Unknown %d", type);
	return buff;
}

void undo_dump()
{
	size_t n;
	int last_serial = -2;
	for(n = 0; n < UndoN; n++) {
		if (last_serial != UndoList[n].Serial) {
			printf("--- serial=%d\n", UndoList[n].Serial);
			last_serial = UndoList[n].Serial;
		}
		printf(" type=%s kind=%d ID=%d\n", undo_type2str(UndoList[n].Type), UndoList[n].Kind, UndoList[n].ID);
	}
}

#endif
