/** \file cstruct.c
 * T_STRUCTURE
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) 2005 Dave Bullis
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "compound.h"
#include "cselect.h"
#include "cundo.h"
#include "custom.h"
#include "fileio.h"
#include "layout.h"
#include "param.h"
#include "cselect.h"
#include "include/paramfile.h"
#include "track.h"
#include "tbezier.h"
#include "ccurve.h"
#include "common-ui.h"

EXPORT TRKTYP_T T_STRUCTURE = -1;

EXPORT dynArr_t structureInfo_da;


static wIndex_t pierListInx;
EXPORT turnoutInfo_t * curStructure = NULL;
static int log_structure = 0;

static wMenu_p structPopupM;

static drawCmd_t structureD = {
	NULL,
	&screenDrawFuncs,
	0,
	1.0,
	0.0,
	{0.0,0.0}, {0.0,0.0},
	Pix2CoOrd, CoOrd2Pix
};

static wIndex_t structureHotBarCmdInx;
static wIndex_t structureInx;
static long hideStructureWindow;
static void RedrawStructure( wDraw_p d, void * context, wWinPix_t x,
                             wWinPix_t y );

static wWinPix_t structureListWidths[] = { 80, 80, 220 };
static const char * structureListTitles[] = { N_("Manufacturer"), N_("Part No"), N_("Description") };
static paramListData_t listData = { 13, 400, 3, structureListWidths, structureListTitles };
static const char * hideLabels[] = { N_("Hide"), NULL };
static paramDrawData_t structureDrawData = { 490, 200, RedrawStructure, NULL, &structureD };
static paramData_t structurePLs[] = {
#define I_LIST	(0)
#define structureListL	((wList_p)structurePLs[I_LIST].control)
	{	PD_LIST, &structureInx, "list", PDO_NOPREF|PDO_DLGRESIZEW, &listData, NULL, BL_DUP },
#define I_DRAW	(1)
	{	PD_DRAW, NULL, "canvas", PDO_NOPSHUPD|PDO_DLGUNDERCMDBUTT|PDO_DLGRESIZE, &structureDrawData, NULL, 0 },
#define I_HIDE	(2)
	{	PD_TOGGLE, &hideStructureWindow, "hide", PDO_DLGCMDBUTTON, hideLabels, NULL, BC_NOBORDER },
#define I_MSGSCALE		(3)
	{	PD_MESSAGE, NULL, NULL, 0, I2VP(80) },
#define I_MSGWIDTH		(4)
	{	PD_MESSAGE, NULL, NULL, 0, I2VP(80) },
#define I_MSGHEIGHT		(5)
	{	PD_MESSAGE, NULL, NULL, 0, I2VP(80) }
};
static paramGroup_t structurePG = { "structure", 0, structurePLs, COUNT( structurePLs ) };



/****************************************
 *
 * STRUCTURE LIST MANAGEMENT
 *
 */




EXPORT turnoutInfo_t * CreateNewStructure(
        char * scale,
        char * title,
        wIndex_t segCnt,
        trkSeg_p segData,
        BOOL_T updateList )
{
	turnoutInfo_t * to;
#ifdef REORIGSTRUCT
	coOrd orig;
#endif

	if (segCnt == 0) {
		return NULL;
	}
	to = FindCompound( FIND_STRUCT, scale, title );
	if (to == NULL) {
		DYNARR_APPEND( turnoutInfo_t *, structureInfo_da, 10 );
		to = (turnoutInfo_t*)MyMalloc( sizeof *to );
		structureInfo(structureInfo_da.cnt-1) = to;
		to->title = MyStrdup( title );
		to->scaleInx = LookupScale( scale );
	}
	to->segCnt = segCnt;
	to->segs = (trkSeg_p)memdup( segData, (sizeof *segData) * segCnt );
	CopyPoly(to->segs,segCnt);
	FixUpBezierSegs(to->segs, segCnt);
	GetSegBounds( zero, 0.0, to->segCnt, to->segs, &to->orig, &to->size );
#ifdef REORIGSTRUCT
	GetSegBounds( zero, 0.0, to->segCnt, to->segs, &orig, &to->size );
	orig.x = - orig.x;
	orig.y = - orig.y;
	MoveSegs( to->segCnt, to->segs, orig );
	to->orig = zero;
#endif
	to->paramFileIndex = curParamFileIndex;
	if (curParamFileIndex == PARAM_CUSTOM) {
		to->contentsLabel = MyStrdup("Custom Structures");
	} else {
		to->contentsLabel = curSubContents;
	}
	to->endCnt = 0;
	SetParamPaths( to, NULL );
	if (updateList && structureListL != NULL) {
		FormatCompoundTitle( LABEL_TABBED|LABEL_MANUF|LABEL_PARTNO|LABEL_DESCR,
		                     to->title );
		if (message[0] != '\0') {
			wListAddValue( structureListL, message, NULL, to );
		}
	}

	to->barScale = curBarScale>0?curBarScale:-1;
	return to;
}

/**
 * Delete a structure definition from memory.
 * \TODO Find a better way to handle Custom Structures (see CreateNewStructure)
 *
 * \param [IN] structure the structure to be deleted
 */

BOOL_T
StructureDelete(void *structure)
{
	turnoutInfo_t * to = (turnoutInfo_t *)structure;
	MyFree(to->title);
	MyFree(to->segs);

	if (to->special) {
		switch(to->special) {
		case TOpier:
			MyFree(to->u.pier.name);
			to->u.pier.name = NULL;
			break;
		case TOpierInfo:
			for(int pierInx=0; pierInx<to->u.pierInfo.cnt; pierInx++) {
				if (to->u.pierInfo.info[pierInx].name) {
					MyFree(to->u.pierInfo.info[pierInx].name);
				}
				to->u.pierInfo.info[pierInx].name = NULL;
			}
			MyFree(to->u.pierInfo.info);
			to->u.pierInfo.cnt = 0;
			break;
		default:;
		}
	}

	MyFree(to);
	return(TRUE);
}

/**
 * Delete all structure definitions that came from a specific parameter
 * file. Due to the way the definitions are loaded from file it is safe to
 * assume that they form a contiguous block in the array.
 *
 * \param  fileIndex parameter file.
 */

void
DeleteStructures(int fileIndex)
{
	int inx = 0;
	int startInx = -1;
	int cnt = 0;

	// go to the start of the block
	while (inx < structureInfo_da.cnt &&
	       structureInfo(inx)->paramFileIndex != fileIndex) {
		startInx = inx++;
	}

	// delete them
	for (; inx < structureInfo_da.cnt &&
	     structureInfo(inx)->paramFileIndex == fileIndex; inx++) {
		turnoutInfo_t * to = structureInfo(inx);
		if (to->paramFileIndex == fileIndex) {
			StructureDelete(to);
			cnt++;
		}
	}

	// copy down the rest of the list to fill the gap
	startInx++;
	while (inx < structureInfo_da.cnt) {
		structureInfo(startInx++) = structureInfo(inx++);
	}

	// and reduce the actual number
	structureInfo_da.cnt -= cnt;
}

/**
 * Check to find out to what extent the contents of the parameter file can be used with
 * the current layout scale / gauge.
 *
 * If parameter scale == layout we have an exact fit.
 * If parameter scale == layout scale +/15% we have compatible track.
 *
 * \param paramFileIndex
 * \param scaleIndex
 * \return
 */
enum paramFileState
GetStructureCompatibility(int paramFileIndex, SCALEINX_T scaleIndex) {
	int i;
	enum paramFileState ret = PARAMFILE_NOTUSABLE;

	if (!IsParamValid(paramFileIndex))
	{
		return(PARAMFILE_UNLOADED);
	}

	//Loop over all entries until an exact fit is found if none return if compatibles were found

	for (i = 0; i < structureInfo_da.cnt; i++)
	{
		turnoutInfo_t *to = structureInfo(i);
		if (to->paramFileIndex == paramFileIndex) {
			SCALE_FIT_T fit = CompatibleScale(FIT_STRUCTURE,to->scaleInx,scaleIndex);
			if (fit == FIT_EXACT) {
				ret = PARAMFILE_FIT;
				break;
			}
			//Within 15% of scale
			if (fit == FIT_COMPATIBLE) {
				ret = PARAMFILE_COMPATIBLE;
			}
		}

	}
	return(ret);
}

static BOOL_T ReadStructureParam(
        char * firstLine )
{
	char scale[10];
	char *title;
	turnoutInfo_t * to;
	char * cp;
	static dynArr_t pierInfo_da;
#define pierInfo(N) DYNARR_N( pierInfo_t, pierInfo_da, N )

	if ( !GetArgs( firstLine+10, "sq", scale, &title ) ) {
		return FALSE;
	}
	if ( !ReadSegs() ) {
		return FALSE;
	}
	to = CreateNewStructure( scale, title, tempSegs_da.cnt, &tempSegs(0), FALSE );
	if (to == NULL) {
		return FALSE;
	}
	if (tempSpecial[0] != '\0') {
		if (strncmp( tempSpecial, PIER, strlen(PIER) ) == 0) {
			DYNARR_RESET( pierInfo_t, pierInfo_da );
			to->special = TOpierInfo;
			cp = tempSpecial+strlen(PIER);
			while (cp) {
				DYNARR_APPEND( pierInfo_t, pierInfo_da, 10 );
				if ( !GetArgs( cp, "fqc", &pierInfo(pierInfo_da.cnt-1).height,
				               &pierInfo(pierInfo_da.cnt-1).name, &cp ) ) {
					return FALSE;
				}
			}
			to->u.pierInfo.cnt = pierInfo_da.cnt;
			to->u.pierInfo.info = (pierInfo_t*)MyMalloc( pierInfo_da.cnt * sizeof *
			                      (pierInfo_t*)NULL );
			memcpy( to->u.pierInfo.info, &pierInfo(0),
			        pierInfo_da.cnt * sizeof *(pierInfo_t*)NULL );
		} else {
			InputError("Unknown special case", TRUE);
		}
	}
	if (tempCustom[0] != '\0') {
		to->customInfo = MyStrdup( tempCustom );
	}
	MyFree( title );
	return TRUE;
}


EXPORT turnoutInfo_t * StructAdd( long mode, SCALEINX_T scale, wList_p list,
                                  coOrd * maxDim )
{
	wIndex_t inx;
	turnoutInfo_t * to, *to1=NULL;
	structureInx = 0;
	for ( inx = 0; inx < structureInfo_da.cnt; inx++ ) {
		to = structureInfo(inx);
		if ( IsParamValid(to->paramFileIndex) &&
		     to->segCnt > 0 &&
		     (FIT_NONE != CompatibleScale( FIT_STRUCTURE, to->scaleInx, scale )) &&
		     to->segCnt != 0 ) {
			if (to1 == NULL) {
				to1 = to;
			}
			if ( to == curStructure ) {
				to1 = to;
				structureInx = wListGetCount( list );
			}
			FormatCompoundTitle( mode, to->title );
			if (message[0] != '\0') {
				wListAddValue( list, message, NULL, to );
				if (maxDim) {
					if (to->size.x > maxDim->x) {
						maxDim->x = to->size.x;
					}
					if (to->size.y > maxDim->y) {
						maxDim->y = to->size.y;
					}
				}
			}
		}
	}
	return to1;
}


/****************************************
 *
 * GENERIC FUNCTIONS
 *
 */

static void DrawStructure(
        track_p t,
        drawCmd_p d,
        wDrawColor color )
{
	struct extraDataCompound_t *xx = GET_EXTRA_DATA(t, T_STRUCTURE,
	                                 extraDataCompound_t);

	d->options &= ~DC_NOTSOLIDLINE;
	switch(xx->lineType) {
	case DRAWLINESOLID:
		break;
	case DRAWLINEDASH:
		d->options |= DC_DASH;
		break;
	case DRAWLINEDOT:
		d->options |= DC_DOT;
		break;
	case DRAWLINEDASHDOT:
		d->options |= DC_DASHDOT;
		break;
	case DRAWLINEDASHDOTDOT:
		d->options |= DC_DASHDOTDOT;
		break;
	case DRAWLINECENTER:
		d->options |= DC_CENTER;
		break;
	case DRAWLINEPHANTOM:
		d->options |= DC_CENTER;
		break;
	}
	DrawSegs( d, xx->orig, xx->angle, xx->segs, xx->segCnt, 0.0, color );
	d->options &= ~DC_NOTSOLIDLINE;
	if ( ((d->options & DC_SIMPLE)==0) &&
	     (labelWhen == 2 || (labelWhen == 1 && (d->options&DC_PRINT))) &&
	     labelScale >= d->scale &&
	     ( GetTrkBits( t ) & TB_HIDEDESC ) == 0 ) {
		DrawCompoundDescription( t, d, color );
	}
}


static BOOL_T ReadStructure(
        char * line )
{
	return ReadCompound( line+10, T_STRUCTURE );
}


static ANGLE_T GetAngleStruct(
        track_p trk,
        coOrd pos,
        EPINX_T * ep0,
        EPINX_T * ep1 )
{
	struct extraDataCompound_t * xx = GET_EXTRA_DATA(trk, T_STRUCTURE,
	                                  extraDataCompound_t);
	ANGLE_T angle;

	pos.x -= xx->orig.x;
	pos.y -= xx->orig.y;
	Rotate( &pos, zero, -xx->angle );
	angle = GetAngleSegs( xx->segCnt, xx->segs, &pos, NULL, NULL, NULL, NULL, NULL);
	if ( ep0 ) { *ep0 = -1; }
	if ( ep1 ) { *ep1 = -1; }
	return NormalizeAngle( angle+xx->angle );
}


static BOOL_T QueryStructure( track_p trk, int query )
{
	switch ( query ) {
	case Q_HAS_DESC:
		return TRUE;
	case Q_IS_STRUCTURE:
		return TRUE;
	default:
		return FALSE;
	}
}


static wBool_t CompareStruct( track_cp trk1, track_cp trk2 )
{
	struct extraDataCompound_t *xx1 = GET_EXTRA_DATA( trk1, T_STRUCTURE,
	                                  extraDataCompound_t );
	struct extraDataCompound_t *xx2 = GET_EXTRA_DATA( trk2, T_STRUCTURE,
	                                  extraDataCompound_t );
	char * cp = message + strlen(message);
	REGRESS_CHECK_POS( "Orig", xx1, xx2, orig )
	REGRESS_CHECK_ANGLE( "Angle", xx1, xx2, angle )
	REGRESS_CHECK_INT( "Flipped", xx1, xx2, flipped )
	REGRESS_CHECK_INT( "Ungrouped", xx1, xx2, ungrouped )
	REGRESS_CHECK_INT( "Split", xx1, xx2, split )
	/* desc orig is not stable
	REGRESS_CHECK_POS( "DescOrig", xx1, xx2, descriptionOrig ) */
	REGRESS_CHECK_POS( "DescOff", xx1, xx2, descriptionOff )
	REGRESS_CHECK_POS( "DescSize", xx1, xx2, descriptionSize )
	return CompareSegs( xx1->segs, xx1->segCnt, xx1->segs, xx1->segCnt );
}

static trackCmd_t structureCmds = {
	"STRUCTURE",
	DrawStructure,
	DistanceCompound,
	DescribeCompound,
	DeleteCompound,
	WriteCompound,
	ReadStructure,
	MoveCompound,
	RotateCompound,
	RescaleCompound,
	NULL,
	GetAngleStruct,
	NULL,	/* split */
	NULL,	/* traverse */
	EnumerateCompound,
	NULL,	/* redraw */
	NULL,	/* trim */
	NULL,	/* merge */
	NULL,	/* modify */
	NULL,	/* getLength */
	NULL,	/* getTrkParams */
	NULL,	/* moveEndPt */
	QueryStructure,
	UngroupCompound,
	FlipCompound,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	CompareStruct
};

static paramData_t pierPLs[] = {
	{	PD_DROPLIST, &pierListInx, "inx", 0, I2VP(50), N_("Pier Number") }
};
static paramGroup_t pierPG = { "structure-pier", 0, pierPLs, COUNT( pierPLs ) };
#define pierL ((wList_p)pierPLs[0].control)

static void ShowPierL( void )
{
	int inx;
	wIndex_t currInx;
	wControl_p controls[2];
	char * labels[1];

	if ( curStructure->special==TOpierInfo && curStructure->u.pierInfo.cnt > 1) {
		if (pierL == NULL) {
			ParamCreateControls( &pierPG, NULL );
		}
		currInx = wListGetIndex( pierL );
		wListClear( pierL );
		for (inx=0; inx<curStructure->u.pierInfo.cnt; inx++) {
			wListAddValue( pierL, curStructure->u.pierInfo.info[inx].name, NULL, NULL );
		}
		if ( currInx < 0 ) {
			currInx = 0;
		}
		if ( currInx >= curStructure->u.pierInfo.cnt ) {
			currInx = curStructure->u.pierInfo.cnt-1;
		}
		wListSetIndex( pierL, currInx );
		controls[0] = (wControl_p)pierL;
		controls[1] = NULL;
		labels[0] = N_("Pier Number");
		InfoSubstituteControls( controls, labels );
	} else {
		InfoSubstituteControls( NULL, NULL );
	}
}


/*****************************************
 *
 *	 Structure Dialog
 *
 */

static void NewStructure();
static coOrd maxStructureDim;
static wWin_p structureW;


static void RescaleStructure( void )
{
	DIST_T xscale, yscale;
	wWinPix_t ww, hh;
	DIST_T w, h;
	wDrawGetSize( structureD.d, &ww, &hh );
	w = ww/structureD.dpi - 0.2;
	h = hh/structureD.dpi - 0.2;
	if (curStructure) {
		xscale = curStructure->size.x/w;
		yscale = curStructure->size.y/h;
	} else {
		xscale = yscale = 0;
	}
	structureD.scale = ceil(max(xscale,yscale));
	structureD.size.x = (w+0.2)*structureD.scale;
	structureD.size.y = (h+0.2)*structureD.scale;
	return;
}


static void structureChange( long changes )
{
	static char * lastScaleName = NULL;
	if (structureW == NULL) {
		return;
	}
	wListSetIndex( structureListL, 0 );
	if ( (!wWinIsVisible(structureW)) ||
	     ( ((changes&CHANGE_SCALE) == 0  || lastScaleName == curScaleName) &&
	       (changes&CHANGE_PARAMS) == 0 ) ) {
		return;
	}
	lastScaleName = curScaleName;
	//curStructure = NULL;
	wControlShow( (wControl_p)structureListL, FALSE );
	wListClear( structureListL );
	maxStructureDim.x = maxStructureDim.y = 0.0;
	if (structureInfo_da.cnt <= 0) {
		return;
	}
	curStructure = StructAdd( LABEL_TABBED|LABEL_MANUF|LABEL_PARTNO|LABEL_DESCR,
	                          GetLayoutCurScale(), structureListL, &maxStructureDim );
	wListSetIndex( structureListL, structureInx );
	wControlShow( (wControl_p)structureListL, TRUE );
	if (curStructure == NULL) {
		wDrawClear( structureD.d );
		return;
	}
	maxStructureDim.x += 2*trackGauge;
	maxStructureDim.y += 2*trackGauge;
	/*RescaleStructure();*/
	RedrawStructure( structureD.d, NULL, 0, 0 );
	return;
}



static void RedrawStructure( wDraw_p d, void * context, wWinPix_t x,
                             wWinPix_t y )
{
	RescaleStructure();
	LOG( log_structure, 2, ( "SelStructure(%s)\n",
	                         (curStructure?curStructure->title:"<NULL>") ) )
	wDrawClear( structureD.d );
	if (curStructure == NULL) {
		return;
	}
	structureD.orig.x = -0.10*structureD.scale + curStructure->orig.x;
	structureD.orig.y = (curStructure->size.y + curStructure->orig.y) -
	                    structureD.size.y + trackGauge;
	DrawSegs( &structureD, zero, 0.0, curStructure->segs, curStructure->segCnt,
	          0.0, wDrawColorBlack );
	sprintf( message, _("Scale %d:1"), (int)structureD.scale );
	ParamLoadMessage( &structurePG, I_MSGSCALE, message );
	sprintf( message, _("Width %s"), FormatDistance(curStructure->size.x) );
	ParamLoadMessage( &structurePG, I_MSGWIDTH, message );
	sprintf( message, _("Height %s"), FormatDistance(curStructure->size.y) );
	ParamLoadMessage( &structurePG, I_MSGHEIGHT, message );
}


static void StructureDlgUpdate(
        paramGroup_p pg,
        int inx,
        void * valueP )
{
	turnoutInfo_t * to;
	if ( inx != I_LIST ) { return; }
	to = (turnoutInfo_t*)wListGetItemContext( (wList_p)pg->paramPtr[inx].control,
	                (wIndex_t)*(long*)valueP );
	NewStructure();
	curStructure = to;
	ShowPierL();
	RedrawStructure( structureD.d, NULL, 0, 0 );
	/* ParamDialogOkActive( &structurePG, FALSE ); */
}


static void DoStructOk( void )
{
	NewStructure();
	Reset();
}



/****************************************
 *
 * GRAPHICS COMMANDS
 *
 */

/*
 * STATE INFO
 */
static struct {
	int state;
	coOrd pos;
	ANGLE_T angle;
} Dst;

static track_p pierTrk;
static EPINX_T pierEp;

static dynArr_t anchors_da;
#define anchors(N) DYNARR_N(trkSeg_t,anchors_da,N)

void static CreateArrowAnchor(coOrd pos,ANGLE_T a,DIST_T len)
{
	DYNARR_APPEND(trkSeg_t,anchors_da,1);
	int i = anchors_da.cnt-1;
	anchors(i).type = SEG_STRLIN;
	anchors(i).lineWidth = 0;
	anchors(i).u.l.pos[0] = pos;
	Translate(&anchors(i).u.l.pos[1],pos,NormalizeAngle(a+135),len);
	anchors(i).color = wDrawColorBlue;
	DYNARR_APPEND(trkSeg_t,anchors_da,1);
	i = anchors_da.cnt-1;
	anchors(i).type = SEG_STRLIN;
	anchors(i).lineWidth = 0;
	anchors(i).u.l.pos[0] = pos;
	Translate(&anchors(i).u.l.pos[1],pos,NormalizeAngle(a-135),len);
	anchors(i).color = wDrawColorBlue;
}

void static CreateRotateAnchor(coOrd pos)
{
	DIST_T d = tempD.scale*0.15;
	DYNARR_APPEND(trkSeg_t,anchors_da,1);
	int i = anchors_da.cnt-1;
	anchors(i).type = SEG_CRVLIN;
	anchors(i).lineWidth = 0.5;
	anchors(i).u.c.center = pos;
	anchors(i).u.c.a0 = 180.0;
	anchors(i).u.c.a1 = 360.0;
	anchors(i).u.c.radius = d*2;
	anchors(i).color = wDrawColorAqua;
	coOrd head;					//Arrows
	for (int j=0; j<3; j++) {
		Translate(&head,pos,j*120,d*2);
		CreateArrowAnchor(head,NormalizeAngle((j*120)+90),d);
	}
}

void static CreateMoveAnchor(coOrd pos)
{
	DYNARR_SET(trkSeg_t,anchors_da,anchors_da.cnt+5);
	DrawArrowHeads(&DYNARR_N(trkSeg_t,anchors_da,anchors_da.cnt-5),pos,0,TRUE,
	               wDrawColorBlue);
	DYNARR_SET(trkSeg_t,anchors_da,anchors_da.cnt+5);
	DrawArrowHeads(&DYNARR_N(trkSeg_t,anchors_da,anchors_da.cnt-5),pos,90,TRUE,
	               wDrawColorBlue);
}

static ANGLE_T PlaceStructure(
        coOrd p0,
        coOrd p1,
        coOrd origPos,
        coOrd * resPos,
        ANGLE_T * resAngle )
{
	coOrd p2 = p1;
	if (curStructure->special == TOpierInfo) {
		pierTrk = OnTrack( &p1, FALSE, TRUE );
		if (pierTrk != NULL) {
			if (((MyGetKeyState() & WKEY_ALT)==0) == magneticSnap ) {
				if (GetTrkType(pierTrk) == T_TURNOUT) {
					pierEp = PickEndPoint( p1, pierTrk );
					if (pierEp >= 0) {
						*resPos = GetTrkEndPos(pierTrk, pierEp);
						*resAngle = NormalizeAngle(GetTrkEndAngle(pierTrk, pierEp)-90.0);
						return TRUE;
					}
				}
			}
			*resAngle = NormalizeAngle(GetAngleAtPoint( pierTrk, p1, NULL, NULL )+90.0);
			if ( NormalizeAngle( FindAngle( p1, p2 ) - *resAngle + 90.0 ) > 180.0 ) {
				*resAngle = NormalizeAngle( *resAngle + 180.0 );
			}
			*resPos = p1;
			return TRUE;
		}
	}
	resPos->x = origPos.x + p1.x - p0.x;
	resPos->y = origPos.y + p1.y - p0.y;
	return FALSE;
}


static void NewStructure( void )
{
	track_p trk;
	struct extraDataCompound_t *xx;
	wIndex_t pierInx;

	CHECK( curStructure->segCnt >= 1 );
	if (Dst.state == 0) {
		return;
	}
	if (curStructure->special == TOpierInfo &&
	    curStructure->u.pierInfo.cnt>1 &&
	    wListGetIndex(pierL) == -1) {
		return;
	}
	UndoStart( _("Place Structure"), "newStruct" );
	trk = NewCompound( T_STRUCTURE, 0, Dst.pos, Dst.angle, curStructure->title, 0,
	                   NULL, NULL, curStructure->segCnt, curStructure->segs );
	xx = GET_EXTRA_DATA(trk, T_STRUCTURE, extraDataCompound_t);
	switch(curStructure->special) {
	case TOnormal:
		xx->special = TOnormal;
		break;
	case TOpierInfo:
		xx->special = TOpier;
		if (curStructure->u.pierInfo.cnt>1) {
			pierInx = wListGetIndex(pierL);
			if (pierInx < 0 || pierInx >= curStructure->u.pierInfo.cnt) {
				pierInx = 0;
			}
		} else {
			pierInx = 0;
		}
		xx->u.pier.height = curStructure->u.pierInfo.info[pierInx].height;
		xx->u.pier.name = curStructure->u.pierInfo.info[pierInx].name;
		if (pierTrk != NULL && xx->u.pier.height >= 0 ) {
			UpdateTrkEndElev( pierTrk, pierEp, ELEV_DEF, xx->u.pier.height, NULL );
		}
		break;
	default:
		CHECKMSG( FALSE, ("bad special %d", (int) (curStructure->special) ) );
	}

	SetTrkVisible( trk, TRUE );
	SetTrkNoTies( trk, FALSE);
	SetTrkBridge( trk, FALSE);
	SetTrkRoadbed( trk, FALSE);

	DrawNewTrack( trk );
	/*DrawStructure( trk, &mainD, wDrawColorBlack, 0 );*/

	UndoEnd();
	Dst.state = 0;
	Dst.angle = 0.0;
}


static void StructRotate( void * pangle )
{
	if (Dst.state == 0) {
		return;
	}
	ANGLE_T angle = (ANGLE_T)VP2L(pangle);
	angle /= 1000.0;
	Dst.pos = cmdMenuPos;
	Rotate( &Dst.pos, cmdMenuPos, angle );
	Dst.angle += angle;
	TempRedraw(); // StructRotate
}


EXPORT STATUS_T CmdStructureAction(
        wAction_t action,
        coOrd pos )
{

	ANGLE_T angle;
	static BOOL_T validAngle;
	static ANGLE_T baseAngle;
	static coOrd origPos;
	static ANGLE_T origAngle;
	static coOrd rot0, rot1;

	switch (action & 0xFF) {

	case C_START:
		if (!magneticSnap) {
			InfoMessage(_("+Alt for Magnetic Snap"));
		} else {
			InfoMessage(_("+Alt to inhibit Magnetic Snap"));
		}

		DYNARR_RESET(trkSeg_t,anchors_da);
		Dst.state = 0;
		Dst.angle = 0.0;
		ShowPierL();
		SetAllTrackSelect( FALSE );
		return C_CONTINUE;

	case wActionMove:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if (Dst.state && (MyGetKeyState()&WKEY_CTRL)) {
			CreateRotateAnchor(pos);
		} else {
			CreateMoveAnchor(pos);
		}
		return C_CONTINUE;
		break;

	case C_DOWN:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if ( curStructure == NULL ) { return C_CONTINUE; }
		ShowPierL();
		if ((MyGetKeyState()&WKEY_ALT) == 0) {
			angle = Dst.angle;
			if (SnapPosAngle(&pos, &angle)) {
				Dst.angle = angle;
			}
		}
		Dst.pos = pos;
		rot0 = pos;
		origPos = Dst.pos;
		PlaceStructure( rot0, pos, origPos, &Dst.pos, &Dst.angle );
		Dst.state = 1;
		InfoMessage( _("Drag to place") );
		CreateMoveAnchor(pos);
		return C_CONTINUE;

	case C_MOVE:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if ( curStructure == NULL ) { return C_CONTINUE; }
		if ((MyGetKeyState()&WKEY_ALT) == 0) {
			angle = Dst.angle;
			if (SnapPosAngle(&pos, &angle)) {
				Dst.angle = angle;
			}
		}
		PlaceStructure( rot0, pos, origPos, &Dst.pos, &Dst.angle );
		CreateMoveAnchor(pos);
		if (!magneticSnap) {
			InfoMessage(_("+Alt for Magnetic Snap"));
		} else {
			InfoMessage(_("+Alt to inhibit Magnetic Snap"));
		}
		// InfoMessage( "[ %0.3f %0.3f ]", pos.x - origPos.x, pos.y - origPos.y );
		return C_CONTINUE;

	case C_RDOWN:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if ( curStructure == NULL ) { return C_CONTINUE; }
		if (Dst.state == 0) {
			Dst.pos = pos;		// If first, use pos, otherwise use current
		}
		rot0 = rot1 = pos;
		CreateRotateAnchor(pos);
		origPos = Dst.pos;
		origAngle = Dst.angle;
		InfoMessage( _("Drag to rotate") );
		Dst.state = 2;
		validAngle = FALSE;
		return C_CONTINUE;

	case C_RMOVE:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if ( curStructure == NULL ) { return C_CONTINUE; }
		rot1 = pos;
		if ( FindDistance( rot0, rot1 ) > (6.0/BASE_DPI)*mainD.scale ) {
			angle = FindAngle( rot0, rot1 );
			if (!validAngle) {
				baseAngle = angle;
				validAngle = TRUE;
			}
			angle -= baseAngle;
			Dst.pos = origPos;
			Dst.angle = NormalizeAngle( origAngle + angle );
			Rotate( &Dst.pos, rot0, angle );
		}
		// InfoMessage( _("Angle = %0.3f"), Dst.angle );
		Dst.state = 2;
		CreateRotateAnchor(rot0);
		return C_CONTINUE;

	case C_RUP:
	case C_UP:
		DYNARR_RESET(trkSeg_t,anchors_da);
		CreateMoveAnchor(pos);
		Dst.state = 1;
		InfoMessage(
		        _("Left-Drag to place, Ctrl+Left-Drag or Right-Drag to Rotate, Space or Enter to accept, Esc to Cancel"));
		return C_CONTINUE;

	case C_CMDMENU:
		DYNARR_RESET(trkSeg_t,anchors_da);
		menuPos = pos;
		wMenuPopupShow( structPopupM );
		return C_CONTINUE;

	case C_REDRAW:
		wSetCursor(mainD.d,defaultCursor);
		if (Dst.state) {
			DrawSegs( &tempD, Dst.pos, Dst.angle,
			          curStructure->segs, curStructure->segCnt, 0.0, selectedColor );
		}
		DrawSegsDA( &tempD, NULL, zero, 0.0, &anchors_da, trackGauge, wDrawColorBlack,
		            0 );
		if (anchors_da.cnt>0) {
			wSetCursor(mainD.d,wCursorNone);
		}
		if (Dst.state == 2) {
			DrawLine( &tempD, rot0, rot1, 0, wDrawColorBlack );
		}
		return C_CONTINUE;

	case C_LCLICK:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if ( curStructure == NULL ) { return C_CONTINUE; }
		CmdStructureAction( C_DOWN, pos );
		CmdStructureAction( C_UP, pos );
		return C_CONTINUE;

	case C_CANCEL:
		DYNARR_RESET(trkSeg_t,anchors_da);
		Dst.state = 0;
		InfoSubstituteControls( NULL, NULL );
		HotBarCancel();
		/*wHide( newTurn.reg.win );*/
		return C_TERMINATE;

	case C_TEXT:
		if ((action>>8) != ' ') {
			return C_CONTINUE;
		}
	/*no break*/
	case C_OK:
		DYNARR_RESET(trkSeg_t,anchors_da);
		NewStructure();
		InfoSubstituteControls( NULL, NULL );
		return C_TERMINATE;

	case C_FINISH:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if (Dst.state != 0) {
			CmdStructureAction( C_OK, pos );
		} else {
			CmdStructureAction( C_CANCEL, pos );
		}
		return C_TERMINATE;

	default:
		return C_CONTINUE;
	}
}


static STATUS_T CmdStructure(
        wAction_t action,
        coOrd pos )
{

	wIndex_t structureIndex;
	turnoutInfo_t * structurePtr;

	switch (action & 0xFF) {

	case C_START:
		if (structureW == NULL) {
			structureW = ParamCreateDialog( &structurePG, MakeWindowTitle(_("Structure")),
			                                _("Close"), (paramActionOkProc)DoStructOk, wHide, TRUE, NULL, F_RESIZE,
			                                StructureDlgUpdate );
			RegisterChangeNotification( structureChange );
		}
		ParamDialogOkActive( &structurePG, FALSE );
		structureIndex = wListGetIndex( structureListL );
		structurePtr = curStructure;
		wShow( structureW );
		structureChange( CHANGE_PARAMS );
		if (curStructure == NULL) {
			NoticeMessage( MSG_STRUCT_NO_STRUCTS, _("Ok"), NULL );
			return C_TERMINATE;
		}
		if (structureIndex > 0 && structurePtr) {
			curStructure = structurePtr;
			wListSetIndex( structureListL, structureIndex );
			RedrawStructure( structureD.d, NULL, 0, 0 );
		}
		InfoMessage( _("Select Structure and then drag to place"));
		ParamLoadControls( &structurePG );
		ParamGroupRecord( &structurePG );
		SetAllTrackSelect( FALSE );
		return CmdStructureAction( action, pos );

	case wActionMove:
		DYNARR_RESET(trkSeg_t,anchors_da);
		if (Dst.state && (MyGetKeyState()&WKEY_CTRL)) {
			CreateRotateAnchor(pos);
		} else {
			CreateMoveAnchor(pos);
		}
		return C_CONTINUE;
		break;
	case C_DOWN:
		if (MyGetKeyState()&WKEY_CTRL) {
			return CmdStructureAction( C_RDOWN, pos );
		}
	/* no break*/
	case C_RDOWN:
		ParamDialogOkActive( &structurePG, TRUE );
		if (hideStructureWindow) {
			wHide( structureW );
		}
		return CmdStructureAction( action, pos );
	case C_MOVE:
		if (MyGetKeyState()&WKEY_CTRL) {
			return CmdStructureAction( C_RMOVE, pos );
		}
	/*no break*/
	case C_RMOVE:
		return CmdStructureAction( action, pos );
	case C_RUP:
	case C_UP:
		if (MyGetKeyState()&WKEY_CTRL) {
			return CmdStructureAction( C_RUP, pos );
		}
		if (hideStructureWindow) {
			wShow( structureW );
		}
		InfoMessage(
		        _("Left drag to move, right drag to rotate, or press Return or click Ok to finalize") );
		return CmdStructureAction( action, pos );
		return C_CONTINUE;

	case C_LCLICK:
		CmdStructureAction( action, pos );
		return C_CONTINUE;

	case C_CANCEL:
		wHide( structureW );
		return C_CANCEL;

	case C_REDRAW:
	case C_TEXT:
	case C_OK:
	case C_FINISH:
	case C_CMDMENU:
		return CmdStructureAction( action, pos );

	default:
		return C_CONTINUE;
	}
}



static char * CmdStructureHotBarProc(
        hotBarProc_e op,
        void * data,
        drawCmd_p d,
        coOrd * origP )
{
	turnoutInfo_t * to = (turnoutInfo_t*)data;
	switch ( op ) {
	case HB_SELECT:
		CmdStructureAction( C_FINISH, zero );
		curStructure = to;
		DoCommandB( I2VP(structureHotBarCmdInx) );
		return NULL;
	case HB_LISTTITLE:
		FormatCompoundTitle( listLabels, to->title );
		if (message[0] == '\0') {
			FormatCompoundTitle( listLabels|LABEL_DESCR, to->title );
		}
		return message;
	case HB_BARTITLE:
		FormatCompoundTitle( hotBarLabels<<1, to->title );
		return message;
	case HB_FULLTITLE:
		return to->title;
	case HB_DRAW:
		//origP->x -= to->orig.x;
		//origP->y -= to->orig.y;
		DrawSegs( d, *origP, 0.0, to->segs, to->segCnt, trackGauge, wDrawColorBlack );
		return NULL;
	}
	return NULL;
}


EXPORT void AddHotBarStructures( void )
{
	wIndex_t inx;
	turnoutInfo_t * to;
	for ( inx=0; inx < structureInfo_da.cnt; inx ++ ) {
		to = structureInfo(inx);
		if ( !( IsParamValid(to->paramFileIndex) &&
		        to->segCnt > 0 &&
		        (FIT_NONE != CompatibleScale( FIT_STRUCTURE, to->scaleInx,
		                                      GetLayoutCurScale())) ) )
			/*( (strcmp( to->scale, "*" ) == 0 && strcasecmp( curScaleName, "DEMO" ) != 0 ) ||
			  strncasecmp( to->scale, curScaleName, strlen(to->scale) ) == 0 ) ) )*/
		{
			continue;
		}
		AddHotBarElement( to->contentsLabel, to->size, to->orig, FALSE, FALSE,
		                  to->barScale, to, CmdStructureHotBarProc );
	}
}

static STATUS_T CmdStructureHotBar(
        wAction_t action,
        coOrd pos )
{
	switch (action & 0xFF) {

	case C_START:
		//structureChange( CHANGE_PARAMS );
		if (curStructure == NULL) {
			NoticeMessage( MSG_STRUCT_NO_STRUCTS, _("Ok"), NULL );
			return C_TERMINATE;
		}
		FormatCompoundTitle( listLabels|LABEL_DESCR, curStructure->title );
		InfoMessage( _("Place %s and draw into position"), message );
		wIndex_t listIndex = FindListItemByContext( structureListL, curStructure );
		if ( listIndex >= 0 ) {
			structureInx = listIndex;
		}
		ParamLoadControls( &structurePG );
		ParamGroupRecord( &structurePG );
		return CmdStructureAction( action, pos );

	case wActionMove:
		return CmdStructureAction( action, pos );

	case C_RDOWN:
	case C_DOWN:
		if (MyGetKeyState()&WKEY_CTRL) {
			return CmdStructureAction( C_RDOWN, pos );
		}
		if ((MyGetKeyState()&WKEY_ALT) == 0) {
			SnapPos(&pos);
		}
		return CmdStructureAction( action, pos );

	case C_RMOVE:
	case C_MOVE:
		if (MyGetKeyState()&WKEY_CTRL) {
			return CmdStructureAction( C_RMOVE, pos );
		}
		if ((MyGetKeyState()&WKEY_ALT) == 0) {
			SnapPos(&pos);
		}
		return CmdStructureAction( action, pos );

	case C_RUP:
	case C_UP:
		if (MyGetKeyState()&WKEY_CTRL) {
			return CmdStructureAction( C_RUP, pos );
		}
		InfoMessage(
		        _("Left-Drag to place, Ctrl+Left-Drag or Right-Drag to Rotate, Space or Enter to accept, Esc to Cancel") );
		return CmdStructureAction( action, pos );

	case C_TEXT:
		if ((action>>8) != ' ') {
			return C_CONTINUE;
		}
	/*no break*/
	case C_OK:
		CmdStructureAction( action, pos );
		return C_CONTINUE;

	case C_CANCEL:
		HotBarCancel();
	/* no break*/
	default:
		return CmdStructureAction( action, pos );
	}
}

#include "bitmaps/building.xpm3"

EXPORT void InitCmdStruct( wMenu_p menu )
{
	AddMenuButton( menu, CmdStructure, "cmdStructure", _("Structure"),
	               wIconCreatePixMap(building_xpm3[iconSize]), LEVEL0_50,
	               IC_WANT_MOVE|IC_STICKY|IC_CMDMENU|IC_POPUP2, ACCL_STRUCTURE, NULL );
	structureHotBarCmdInx = AddMenuButton( menu, CmdStructureHotBar,
	                                       "cmdStructureHotBar", "", NULL, LEVEL0_50,
	                                       IC_WANT_MOVE|IC_STICKY|IC_CMDMENU|IC_POPUP2, 0, NULL );
	ParamRegister( &structurePG );
	if ( structPopupM == NULL ) {
		structPopupM = MenuRegister( "Structure Rotate" );
		AddRotateMenu( structPopupM, StructRotate );
	}
}

EXPORT void InitTrkStruct( void )
{
	T_STRUCTURE = InitObject( &structureCmds );

	log_structure = LogFindIndex( "Structure" );
	AddParam( "STRUCTURE ", ReadStructureParam);
	ParamRegister( &pierPG );
}
