/** \file tstraigh.c
 * Straight track
 */

/*  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 "cstraigh.h"
#include "cundo.h"
#include "fileio.h"
#include "layout.h"
#include "param.h"
#include "track.h"
#include "common-ui.h"

/*******************************************************************************
 *
 * STRAIGHT
 *
 */

static TRKTYP_T T_STRAIGHT = -1;

typedef struct extraDataStraight_t {
	extraDataBase_t base;
	coOrd descriptionOff;
} extraDataStraight_t;


/** @logcmd @showrefby straight=n tstraigh.c */
static int log_straight = 0;

/****************************************
 *
 * UTILITIES
 *
 */


void AdjustStraightEndPt( track_p t, EPINX_T inx, coOrd pos )
{
	CHECKMSG( GetTrkType(t) == T_STRAIGHT,
	          ("AdjustLIneEndPt( %d, %d ) not on STRAIGHT %d\n",
	           GetTrkIndex(t), inx, GetTrkType(t) ) );
	UndoModify( t );
#ifdef VERBOSE
	lprintf("adjustStraightEndPt T%d[%d] p=[%0.3f %0.3f]\n",
	        GetTrkIndex(t), inx, pos.x, pos.y );
#endif
	SetTrkEndPoint( t, inx, pos, GetTrkEndAngle(t,inx));
	ComputeBoundingBox( t );
	CheckTrackLength( t );
}

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

static struct {
	coOrd endPt[2];
	DIST_T elev[2];
	FLOAT_T length;
	ANGLE_T angle;
	FLOAT_T grade;
	descPivot_t pivot;
	unsigned int layerNumber;
} strData;
typedef enum { E0, Z0, E1, Z1, LN, AN, GR, PV, LY } strDesc_e;
static descData_t strDesc[] = {
	/*E0*/	{ DESC_POS, N_("End Pt 1: X,Y"), &strData.endPt[0] },
	/*Z0*/	{ DESC_DIM, N_("Z"), &strData.elev[0] },
	/*E1*/	{ DESC_POS, N_("End Pt 2: X,Y"), &strData.endPt[1] },
	/*Z1*/	{ DESC_DIM, N_("Z"), &strData.elev[1] },
	/*LN*/	{ DESC_DIM, N_("Length"), &strData.length },
	/*AN*/	{ DESC_ANGLE, N_("Track Angle"), &strData.angle },
	/*GR*/	{ DESC_FLOAT, N_("Grade"), &strData.grade },
	/*PV*/	{ DESC_PIVOT, N_("Pivot"), &strData.pivot },
	/*LY*/	{ DESC_LAYER, N_("Layer"), &strData.layerNumber },
	{ DESC_NULL }
};



EXPORT BOOL_T UpdateDescStraight(
        int inx,
        int e0,
        int e1,
        int ln,
        int an,
        descData_p desc,
        long pivot )
{
	coOrd mid;
	if ( inx == e0 || inx == e1 ) {
		*(DIST_T*)desc[ln].valueP = FindDistance( *(coOrd*)desc[e0].valueP,
		                            *(coOrd*)desc[e1].valueP );
		*(ANGLE_T*)desc[an].valueP = FindAngle( *(coOrd*)desc[e0].valueP,
		                                        *(coOrd*)desc[e1].valueP );
		if ( inx == e0 ) {
			desc[e1].mode |= DESC_CHANGE;
		} else {
			desc[e0].mode |= DESC_CHANGE;
		}
		desc[ln].mode |= DESC_CHANGE;
		desc[an].mode |= DESC_CHANGE;
	} else if ( inx == ln || inx == an ) {
		if ( inx == ln && *(DIST_T*)desc[ln].valueP <= minLength ) {
			ErrorMessage( MSG_OBJECT_TOO_SHORT );
			*(DIST_T*)desc[ln].valueP = FindDistance( *(coOrd*)desc[e0].valueP,
			                            *(coOrd*)desc[e1].valueP );
			desc[ln].mode |= DESC_CHANGE;
			return FALSE;
		}
		switch (pivot) {
		case DESC_PIVOT_FIRST:
			Translate( (coOrd*)desc[e1].valueP, *(coOrd*)desc[e0].valueP,
			           *(ANGLE_T*)desc[an].valueP, *(DIST_T*)desc[ln].valueP );
			desc[e1].mode |= DESC_CHANGE;
			break;
		case DESC_PIVOT_SECOND:
			Translate( (coOrd*)desc[e0].valueP, *(coOrd*)desc[e1].valueP,
			           *(ANGLE_T*)desc[an].valueP+180.0, *(DIST_T*)desc[ln].valueP );
			desc[e0].mode |= DESC_CHANGE;
			break;
		case DESC_PIVOT_MID:
			mid.x = (((coOrd*)desc[e0].valueP)->x+((coOrd*)desc[e1].valueP)->x)/2.0;
			mid.y = (((coOrd*)desc[e0].valueP)->y+((coOrd*)desc[e1].valueP)->y)/2.0;
			Translate( (coOrd*)desc[e0].valueP, mid, *(ANGLE_T*)desc[an].valueP+180.0,
			           *(DIST_T*)desc[ln].valueP/2.0 );
			Translate( (coOrd*)desc[e1].valueP, mid, *(ANGLE_T*)desc[an].valueP,
			           *(DIST_T*)desc[ln].valueP/2.0 );
			desc[e0].mode |= DESC_CHANGE;
			desc[e1].mode |= DESC_CHANGE;
			break;
		default:
			break;
		}
	} else {
		return FALSE;
	}
	return TRUE;
}


static void UpdateStraight( track_p trk, int inx, descData_p descUpd,
                            BOOL_T final )
{
	EPINX_T ep;
	switch ( inx ) {
	case E0:
	case E1:
	case LN:
	case AN:
		if ( ! UpdateDescStraight( inx, E0, E1, LN, AN, strDesc, strData.pivot ) ) {
			return;
		}
		break;
	case Z0:
	case Z1:
		ep = (inx==Z0?0:1);
		UpdateTrkEndElev( trk, ep, GetTrkEndElevUnmaskedMode(trk,ep), strData.elev[ep],
		                  NULL );
		ComputeElev( trk, 1-ep, FALSE, &strData.elev[1-ep], NULL, TRUE );
		if ( strData.length > minLength ) {
			strData.grade = fabs( (strData.elev[0]-strData.elev[1])/strData.length )*100.0;
		} else {
			strData.grade = 0.0;
		}
		strDesc[GR].mode |= DESC_CHANGE;
		strDesc[inx==Z0?Z1:Z0].mode |= DESC_CHANGE;
		/*return;*/
		break;
	case LY:
		SetTrkLayer( trk, strData.layerNumber);
		break;
	default:
		return;
	}
	UndrawNewTrack( trk );
	if ( GetTrkEndTrk(trk,0) == NULL ) {
		SetTrkEndPoint( trk, 0, strData.endPt[0],
		                NormalizeAngle(strData.angle+180.0) );
	}
	if ( GetTrkEndTrk(trk,1) == NULL ) {
		SetTrkEndPoint( trk, 1, strData.endPt[1], strData.angle );
	}
	ComputeBoundingBox( trk );
	DrawNewTrack( trk );
}

static void DescribeStraight( track_p trk, char * str, CSIZE_T len )
{
	int fix0, fix1;
	sprintf( str,
	         _("Straight Track(%d): Layer=%d Length=%s EP=[%0.3f,%0.3f A%0.3f] [%0.3f,%0.3f A%0.3f]"),
	         GetTrkIndex(trk),
	         GetTrkLayer(trk)+1,
	         FormatDistance(FindDistance( GetTrkEndPos(trk,0), GetTrkEndPos(trk,1) )),
	         GetTrkEndPosXY(trk,0), GetTrkEndAngle(trk,0),
	         GetTrkEndPosXY(trk,1), GetTrkEndAngle(trk,1) );
	fix0 = GetTrkEndTrk(trk,0)!=NULL;
	fix1 = GetTrkEndTrk(trk,1)!=NULL;
	strData.endPt[0] = GetTrkEndPos(trk,0);
	strData.endPt[1] = GetTrkEndPos(trk,1);
	ComputeElev( trk, 0, FALSE, &strData.elev[0], NULL, FALSE );
	ComputeElev( trk, 1, FALSE, &strData.elev[1], NULL, FALSE );
	strData.length = FindDistance( strData.endPt[0], strData.endPt[1] );
	strData.layerNumber = GetTrkLayer(trk);
	if ( strData.length > minLength ) {
		strData.grade = fabs( (strData.elev[0]-strData.elev[1])/strData.length )*100.0;
	} else {
		strData.grade = 0.0;
	}
	strData.angle = FindAngle( strData.endPt[0], strData.endPt[1] );
	strDesc[E0].mode =
	        strDesc[E1].mode = (fix0|fix1)?DESC_RO:0;
	strDesc[Z0].mode = (EndPtIsDefinedElev(trk,0)?0:DESC_RO)|DESC_NOREDRAW;
	strDesc[Z1].mode = (EndPtIsDefinedElev(trk,1)?0:DESC_RO)|DESC_NOREDRAW;
	strDesc[GR].mode = DESC_RO;
	strDesc[LN].mode = (fix0&fix1)?DESC_RO:0;
	strDesc[AN].mode = (fix0|fix1)?DESC_RO:0;
	strDesc[PV].mode = (fix0|fix1)?DESC_IGNORE:0;
	strDesc[LY].mode = DESC_NOREDRAW;
	strData.pivot = (fix0&fix1)?DESC_PIVOT_NONE:
	                fix0?DESC_PIVOT_FIRST:
	                fix1?DESC_PIVOT_SECOND:
	                DESC_PIVOT_MID;
	DoDescribe( _("Straight Track"), trk, strDesc, UpdateStraight );
}

static DIST_T DistanceStraight( track_p t, coOrd * p )
{
	return LineDistance( p, GetTrkEndPos(t,0), GetTrkEndPos(t,1) );
}

STATUS_T StraightDescriptionMove(
        track_p trk,
        wAction_t action,
        coOrd pos )
{
	extraDataStraight_t *xx = GET_EXTRA_DATA(trk, T_STRAIGHT, extraDataStraight_t);
	ANGLE_T ap;
//	ANGLE_T a;
	coOrd end0, end1;
	end0 = GetTrkEndPos(trk,0);
	end1 = GetTrkEndPos(trk,1);
//  a = FindAngle(end0,end1);
	ap = NormalizeAngle(FindAngle(end0,pos)-FindAngle(end0,end1));

	xx->descriptionOff.y = FindDistance(end0,pos)*sin(D2R(ap))-2*GetTrkGauge(trk);
	xx->descriptionOff.x = -0.5 + FindDistance(end0,
	                       pos)*cos(D2R(ap))/FindDistance(end0,end1);
	if (xx->descriptionOff.x > 0.5) { xx->descriptionOff.x = 0.5; }
	if (xx->descriptionOff.x < -0.5) { xx->descriptionOff.x = -0.5; }


	return C_CONTINUE;

}

DIST_T StraightDescriptionDistance(
        coOrd pos,
        track_p trk,
        coOrd * dpos,
        BOOL_T show_hidden,
        BOOL_T * hidden)
{
	coOrd p1;
	if (hidden) { *hidden = FALSE; }
	if ( GetTrkType( trk ) != T_STRAIGHT
	     || ((( GetTrkBits( trk ) & TB_HIDEDESC ) != 0 ) && !show_hidden)) {
		return DIST_INF;
	}

	struct extraDataStraight_t *xx = GET_EXTRA_DATA(trk, T_STRAIGHT,
	                                 extraDataStraight_t);
	ANGLE_T a;
	coOrd end0, end0off, end1, end1off;
	end0 = GetTrkEndPos(trk,0);
	end1 = GetTrkEndPos(trk,1);
	a = FindAngle(end0,end1);
	Translate(&end0off,end0,a+90,2*GetTrkGauge(trk)+xx->descriptionOff.y);
	Translate(&end1off,end1,a+90,2*GetTrkGauge(trk)+xx->descriptionOff.y);

	p1.x = (end1off.x - end0off.x)*(xx->descriptionOff.x+0.5) + end0off.x;
	p1.y = (end1off.y - end0off.y)*(xx->descriptionOff.x+0.5) + end0off.y;

	if (hidden) { *hidden = (GetTrkBits( trk ) & TB_HIDEDESC); }
	*dpos = p1;
	coOrd tpos  = pos;
	if (LineDistance(&tpos,end0,end1)<FindDistance( p1, pos )) {
		return LineDistance(&pos,end0,end1);
	}
	return FindDistance( p1, pos );
}


static void DrawStraightDescription(
        track_p trk,
        drawCmd_p d,
        wDrawColor color )
{
	ANGLE_T a;
	struct extraDataStraight_t *xx = GET_EXTRA_DATA(trk, T_STRAIGHT,
	                                 extraDataStraight_t);

	if (layoutLabels == 0) {
		return;
	}
	if ((labelEnable&LABELENABLE_TRKDESC)==0) {
		return;
	}

	coOrd end0, end0off, end1, end1off;
	end0 = GetTrkEndPos(trk,0);
	end1 = GetTrkEndPos(trk,1);
	a = FindAngle(end0,end1);
	Translate(&end0off,end0,a+90,2*GetTrkGauge(trk)+xx->descriptionOff.y);
	DrawLine(d,end0,end0off,0,color);
	Translate(&end1off,end1,a+90,2*GetTrkGauge(trk)+xx->descriptionOff.y);
	DrawLine(d,end1,end1off,0,color);
	sprintf( message, "L%s A%0.3f",
	         FormatDistance(FindDistance(end0,end1)),FindAngle(end0,end1));

	DrawDimLine( d, end0off, end1off, message, (wFontSize_t)descriptionFontSize,
	             xx->descriptionOff.x+0.5, 0, color, 0x00 );

	if ( !(GetTrkBits( trk ) & TB_DETAILDESC) ) { return; }

	if ( GetTrkBits( trk ) & TB_DETAILDESC ) {
		coOrd details_pos;
		details_pos.x = (end1off.x - end0off.x)*(xx->descriptionOff.x+0.5) + end0off.x;
		details_pos.y = (end1off.y - end0off.y)*(xx->descriptionOff.x+0.5) + end0off.y-
		                (2*descriptionFontSize/mainD.dpi);

		AddTrkDetails(d, trk, details_pos, FindDistance(end0,end1), color);
	}

}

static void DrawStraight( track_p t, drawCmd_p d, wDrawColor color )
{
	if (((d->options&(DC_SIMPLE|DC_SEGTRACK))==0) &&
	    (labelWhen == 2 || (labelWhen == 1 && (d->options&DC_PRINT))) &&
	    labelScale >= d->scale &&
	    ( GetTrkBits( t ) & TB_HIDEDESC ) == 0 ) {
		DrawStraightDescription( t, d, color );
	}
	// long bridge = GetTrkBridge( t );
	long widthOptions = DTS_LEFT|DTS_RIGHT;
	DrawStraightTrack( d, GetTrkEndPos(t,0), GetTrkEndPos(t,1),
	                   GetTrkEndAngle(t,0),
	                   t, color, widthOptions );
	DrawEndPt( d, t, 0, color );
	DrawEndPt( d, t, 1, color );
}

EXPORT void DrawStraightTies(
        drawCmd_p d,
        tieData_t td,
        coOrd p0,
        coOrd p1,
        wDrawColor color )
{
	DIST_T tieOff0=0.0, tieOff1=0.0;
	DIST_T len, dlen;
	coOrd pos;
	int cnt;
	ANGLE_T angle;

	if ( (d->options&DC_SIMPLE) != 0 ) {
		return;
	}

	len = FindDistance( p0, p1 );
	len -= tieOff0+tieOff1;
	angle = FindAngle( p0, p1 );
	cnt = (int)floor(len / td.spacing+0.5);
	if ( len - td.spacing*cnt - td.width > (td.spacing - td.width)/2 ) {
		cnt++;
	}
	if ( cnt != 0 ) {
		dlen = FindDistance( p0, p1 )/cnt;
//		double endsize = FindDistance( p0, p1 )-cnt*dlen-td->width;
		for ( len=dlen/2; cnt; cnt--,len+=dlen ) {
			Translate( &pos, p0, angle, len );
			DrawTie( d, pos, angle, td.length, td.width, color,
			         tieDrawMode==TIEDRAWMODE_SOLID );
		}
	}
}



EXPORT void DrawStraightTrack(
        drawCmd_p d,
        coOrd p0,
        coOrd p1,
        ANGLE_T angle,
        track_cp trk,
        wDrawColor color,
        long options )
{
	coOrd pp0, pp1;
	DIST_T trackGauge = GetTrkGauge(trk);
	tieData_t td;
	long bridge = 0, roadbed = 0;
	if ( trk ) {
		bridge = GetTrkBridge(trk);
		roadbed = GetTrkRoadbed(trk);
	}
	wDrawWidth width=0;
	trkSeg_p segPtr;

	if ( (d->options&DC_SEGTRACK) ) {
		DYNARR_APPEND( trkSeg_t, tempSegs_da, 10 );
		segPtr = &tempSegs(tempSegs_da.cnt-1);
		segPtr->type = SEG_STRTRK;
		segPtr->lineWidth = 0;
		segPtr->color = wDrawColorBlack;
		segPtr->u.l.pos[0] = p0;
		segPtr->u.l.pos[1] = p1;
		segPtr->u.l.angle = angle;
		segPtr->u.l.option = 0;
		return;
	}

	width = trk ? GetTrkWidth( trk ): 0;
	if ((d->options&DC_PRINT) && (d->dpi>2*BASE_DPI)) {
		width = (wDrawWidth)round(width * d->dpi / 2 / BASE_DPI);
	}

	if ( d->options&DC_THICK ) {
		width = 3;
	}
	if ( color == wDrawColorPreviewSelected
	     || color == wDrawColorPreviewUnselected ) {
		width = 3;
	}


	LOG(log_straight,4,("DST( (%0.3f %0.3f) .. (%0.3f..%0.3f)\n",
	                    p0.x, p0.y, p1.x, p1.y ) )

	// Draw solid background
	if(bridge|roadbed) {
		wDrawWidth width3 = (wDrawWidth)round(trackGauge * 3 * d->dpi / d->scale);
		DrawLine(d,p0,p1,width3,bridge?bridgeColor:roadbedColor);
	}

	if ( DoDrawTies( d, trk ) ) {
		td = GetTrkTieData( trk );
		DrawStraightTies( d, td, p0, p1, color );
	}
	if ( color == wDrawColorBlack ) {
		color = normalColor;
	}
	if ( ! DrawTwoRails( d, 1 ) ) {
		DrawLine( d, p0, p1, width, color );
	} else {
		if ( hasTrackCenterline(d)) {
			long options = d->options;
			d->options |= DC_DASH;
			DrawLine( d, p0, p1, 0, color );
			d->options = options;
		}
		Translate( &pp0, p0, angle+90, trackGauge/2.0 );
		Translate( &pp1, p1, angle+90, trackGauge/2.0 );
		DrawLine( d, pp0, pp1, width, color );

		Translate( &pp0, p0, angle-90, trackGauge/2.0 );
		Translate( &pp1, p1, angle-90, trackGauge/2.0 );
		DrawLine( d, pp0, pp1, width, color );

		if ( (d->options&DC_PRINT) && roadbedWidth > trackGauge && DrawTwoRails(d,1) ) {
			wDrawWidth rbw = (wDrawWidth)floor(roadbedLineWidth*(d->dpi/d->scale)+0.5);
			if ( options&DTS_RIGHT ) {
				Translate( &pp0, p0, angle+90, roadbedWidth/2.0 );
				Translate( &pp1, p1, angle+90, roadbedWidth/2.0 );
				DrawLine( d, pp0, pp1, rbw, color );
			}
			if ( options&DTS_LEFT ) {
				Translate( &pp0, p0, angle-90, roadbedWidth/2.0 );
				Translate( &pp1, p1, angle-90, roadbedWidth/2.0 );
				DrawLine( d, pp0, pp1, rbw, color );
			}
		}
	}

	if (bridge) {
		wDrawWidth width2 = (wDrawWidth)round((2.0 * d->dpi)/BASE_DPI);
		if (d->options&DC_PRINT) {
			width2 = (wDrawWidth)round(d->dpi / BASE_DPI);
		}

		Translate( &pp0, p0, angle-90, trackGauge*1.5 );
		Translate( &pp1, p1, angle-90, trackGauge*1.5 );
		DrawLine( d, pp0, pp1, width2, color );

		Translate( &pp0, p0, angle+90, trackGauge*1.5 );
		Translate( &pp1, p1, angle+90, trackGauge*1.5 );
		DrawLine( d, pp0, pp1, width2, color);
	}
}


static void DeleteStraight( track_p t )
{
}

static BOOL_T WriteStraight( track_p t, FILE * f )
{
	int bits;
	long options;
	struct extraDataStraight_t *xx = GET_EXTRA_DATA(t, T_STRAIGHT,
	                                 extraDataStraight_t);
	BOOL_T rc = TRUE;

	options = GetTrkWidth(t) & 0x0F;
	if ( ( GetTrkBits(t) & TB_HIDEDESC ) == 0 )
		// 0x80 means Show Description
	{
		options |= 0x80;
	}
	bits = GetTrkVisible(t)|(GetTrkNoTies(t)?1<<2:0)|(GetTrkBridge(t)?1<<3:0)|
	       (GetTrkRoadbed(t)?1<<4:0);
	rc &= fprintf(f, "STRAIGHT %d %d %ld 0 0 %s %d %0.6f %0.6f\n",
	              GetTrkIndex(t), GetTrkLayer(t), options,
	              GetTrkScaleName(t), bits, xx->descriptionOff.x, xx->descriptionOff.y )>0;
	rc &= WriteEndPt( f, t, 0 );
	rc &= WriteEndPt( f, t, 1 );
	rc &= fprintf(f, "\t%s\n", END_SEGS)>0;
	return rc;
}

static BOOL_T ReadStraight( char * line )
{
	track_p trk;
	wIndex_t index;
	BOOL_T visible;
	char scale[10];
	wIndex_t layer;
	long options;
	struct extraDataStraight_t *xx;
	char * cp = NULL;
	coOrd descriptionOff = { 0.0, 0.0 };

	if ( !GetArgs( line+8, paramVersion<3?"dXZsdc":"dLl00sdc", &index, &layer,
	               &options, scale, &visible, &cp ) ) {
		return FALSE;
	}
	if (cp) {
		if (!GetArgs(cp,"p",&descriptionOff)) {
			return FALSE;
		}
	}
	if ( !ReadSegs() ) {
		return FALSE;
	}
	trk = NewTrack( index, T_STRAIGHT, 0, sizeof *xx );
	xx = GET_EXTRA_DATA(trk, T_STRAIGHT, extraDataStraight_t);
	xx->descriptionOff = descriptionOff;
	SetTrkScale( trk, LookupScale(scale) );
	if ( paramVersion < 3 ) {
		SetTrkVisible(trk, visible!=0);
		SetTrkNoTies(trk, FALSE);
		SetTrkBridge(trk, FALSE);
		SetTrkRoadbed(trk, FALSE);
	} else {
		SetTrkVisible(trk, visible&2);
		SetTrkNoTies(trk, visible&4);
		SetTrkBridge(trk, visible&8);
		SetTrkRoadbed(trk, visible&16);
	}
	SetTrkLayer(trk, layer);
	SetTrkWidth( trk, (int)(options & 0x0F) );
	SetEndPts( trk, 2 );
	ComputeBoundingBox( trk );
	if ( paramVersion < VERSION_DESCRIPTION2 || ( ( options & 0x80 ) == 0 ) ) {
		SetTrkBits(trk,TB_HIDEDESC);
	}
	return TRUE;
}

static void MoveStraight( track_p trk, coOrd orig )
{
	ComputeBoundingBox( trk );
}

static void RotateStraight( track_p trk, coOrd orig, ANGLE_T angle )
{
	ComputeBoundingBox( trk );
}

static void RescaleStraight( track_p trk, FLOAT_T ratio )
{
}

static int AuditStraight( track_p trk, char * msg )
{
	if (FindDistance( GetTrkEndPos(trk,0), GetTrkEndPos(trk,1) ) < 0.01) {
		sprintf( msg, "T%d: short track\n", GetTrkIndex(trk) );
		return FALSE;
	} else {
		return TRUE;
	}
}


static ANGLE_T GetAngleStraight( track_p trk, coOrd pos, EPINX_T *ep0,
                                 EPINX_T *ep1 )
{
	if ( ep0 ) { *ep0 = 0; }
	if ( ep1 ) { *ep1 = 1; }
	return GetTrkEndAngle( trk, 0 );
}


static BOOL_T SplitStraight( track_p trk, coOrd pos, EPINX_T ep,
                             track_p *leftover, EPINX_T * ep0, EPINX_T * ep1 )
{
	track_p trk1;

	trk1 = NewStraightTrack( 1-ep?GetTrkEndPos(trk,ep):pos,
	                         1-ep?pos:GetTrkEndPos(trk,ep) );
	DIST_T height;
	int opt;
	GetTrkEndElev(trk,ep,&opt,&height);
	UpdateTrkEndElev( trk1, ep, opt, height,
	                  (opt==ELEV_STATION)?GetTrkEndElevStation(trk,ep):NULL );
	AdjustStraightEndPt( trk, ep, pos );
	UpdateTrkEndElev( trk, ep, ELEV_NONE, 0, NULL);
	*leftover = trk1;
	*ep0 = 1-ep;
	*ep1 = ep;
	return TRUE;
}


static BOOL_T TraverseStraight( traverseTrack_p trvTrk, DIST_T * distR )
{
	coOrd pos[2];
	ANGLE_T angle0, angle;
	DIST_T dist;
	track_p trk = trvTrk->trk;
	EPINX_T ep;

	pos[0] = GetTrkEndPos(trk,0);
	pos[1] = GetTrkEndPos(trk,1);
	angle0 = FindAngle( pos[0], pos[1] );
	angle = NormalizeAngle( angle0-trvTrk->angle );
	trvTrk->angle = angle0;
	if ( angle < 270 && angle > 90 ) {
		ep = 0;
		trvTrk->angle = NormalizeAngle( trvTrk->angle + 180.0 );
	} else {
		ep = 1;
	}
	dist = FindDistance( trvTrk->pos, pos[ep] );
	if ( dist > *distR ) {
		Translate( &trvTrk->pos, pos[ep], NormalizeAngle(trvTrk->angle+180.0),
		           dist-*distR );
		*distR = 0;
	} else {
		trvTrk->pos = pos[ep];
		*distR -= dist;
		trvTrk->trk = GetTrkEndTrk( trk, ep );
	}
	return TRUE;
}


static BOOL_T EnumerateStraight( track_p trk )
{
	DIST_T d;
	if (trk != NULL) {
		d = FindDistance( GetTrkEndPos( trk, 0 ), GetTrkEndPos( trk, 1 ) );
		ScaleLengthIncrement( GetTrkScale(trk), d );
		return TRUE;
	}
	return FALSE;
}

static BOOL_T TrimStraight( track_p trk, EPINX_T ep, DIST_T dist, coOrd endpos,
                            ANGLE_T angle, DIST_T radius, coOrd center )
{
	DIST_T d;
	ANGLE_T a;
	coOrd p1, pos;
	a = NormalizeAngle( GetTrkEndAngle(trk,ep) + 180.0 );
	Translate( &pos, GetTrkEndPos(trk,ep), a, dist );
	p1 = GetTrkEndPos( trk, 1-ep );
	d = FindDistance( pos, p1 );
	if (dist < FindDistance( GetTrkEndPos(trk,0), GetTrkEndPos(trk,1) ) &&
	    d > minLength ) {
		UndrawNewTrack( trk );
		AdjustStraightEndPt( trk, ep, pos );
		DrawNewTrack( trk );
	} else {
		UndrawNewTrack( trk );
		DeleteTrack( trk, TRUE );
	}
	return TRUE;
}


BOOL_T ExtendStraightToJoin(
        track_p trk0,
        EPINX_T ep0,
        track_p trk1,
        EPINX_T ep1 )
{
	coOrd off;
	ANGLE_T a;
	track_p trk0x, trk1x, trk2;
	EPINX_T ep0x=-1, ep1x=-1;
	coOrd pos0, pos1;
	ANGLE_T a0, a1, aa;

	a0 = GetTrkEndAngle( trk0, ep0 );
	a1 = GetTrkEndAngle( trk1, ep1 );
	a = NormalizeAngle( a0 - a1 + 180.0 + connectAngle/2.0 );
	pos0 = GetTrkEndPos( trk0, (GetTrkType(trk0) == T_STRAIGHT)?1-ep0:ep0 );
	off = pos1 = GetTrkEndPos( trk1, (GetTrkType(trk1) == T_STRAIGHT)?1-ep1:ep1 );
	Rotate( &off, pos0, -a0 );
	off.x -= pos0.x;

	if ( a >= connectAngle ||
	     !IsClose( fabs(off.x) ) ||
	     off.y-pos0.y <= connectDistance ) {
		return FALSE;
	}

	if ( GetTrkType(trk0) != T_STRAIGHT &&
	     GetTrkType(trk1) != T_STRAIGHT ) {
		aa = FindAngle( pos0, pos1 );
		aa = NormalizeAngle( aa-a0+connectAngle/2.0);
		if (aa > connectAngle) {
			return FALSE;
		}
	}
	UndoStart( _("Extending Straight Track"),
	           "ExtendStraightToJoin( T%d[%d] T%d[%d] )", GetTrkIndex(trk0), ep0,
	           GetTrkIndex(trk1), ep1 );
	UndoModify( trk0 );
	UndoModify( trk1 );
	trk2 = trk0x = trk1x = NULL;
	if ( GetTrkType(trk0) == T_STRAIGHT ) {
		pos0 = GetTrkEndPos( trk0, 1-ep0 );
		trk0x = GetTrkEndTrk( trk0, 1-ep0 );
		if (trk0x) {
			ep0x = GetEndPtConnectedToMe( trk0x, trk0 );
			DisconnectTracks( trk0, 1-ep0, trk0x, ep0x );
		}
		trk2 = trk0;
		UndrawNewTrack( trk2 );
	} else {
		trk0x = trk0;
		ep0x = ep0;
		DrawEndPt( &mainD, trk0, ep0, wDrawColorWhite );
	}
	if ( GetTrkType(trk1) == T_STRAIGHT ) {
		pos1 = GetTrkEndPos( trk1, 1-ep1 );
		trk1x = GetTrkEndTrk( trk1, 1-ep1 );
		if (trk1x) {
			ep1x = GetEndPtConnectedToMe( trk1x, trk1 );
			DisconnectTracks( trk1, 1-ep1, trk1x, ep1x );
		}
		if (trk2) {
			UndrawNewTrack( trk1 );
			DeleteTrack( trk1, TRUE );
		} else {
			trk2 = trk1;
			UndrawNewTrack( trk2 );
		}
	} else {
		trk1x = trk1;
		ep1x = ep1;
		DrawEndPt( &mainD, trk1, ep1, wDrawColorWhite );
	}

	if (trk2) {
		SetTrkEndPoint( trk2, 0, pos0, NormalizeAngle(a0+180.0) );
		SetTrkEndPoint( trk2, 1, pos1, NormalizeAngle(a1+180.0) );
		ComputeBoundingBox( trk2 );
	} else {
		trk2 = NewStraightTrack( pos0, pos1 );
	}
	if (trk0x) {
		ConnectTracks( trk2, 0, trk0x, ep0x );
	}
	if (trk1x) {
		ConnectTracks( trk2, 1, trk1x, ep1x );
	}
	DrawNewTrack( trk2 );

	return TRUE;
}


static STATUS_T ModifyStraight( track_p trk, wAction_t action, coOrd pos )
{
	static EPINX_T ep;
	static BOOL_T valid;
	DIST_T d;

	switch ( action ) {
	case C_DOWN:
		ep = PickUnconnectedEndPoint( pos, trk );
		if (ep == -1) {
			return C_ERROR;
		}
		UndrawNewTrack( trk );
		DYNARR_SET( trkSeg_t, tempSegs_da, 1 );
		tempSegs(0).type = SEG_STRTRK;
		tempSegs(0).lineWidth = 0;
		tempSegs(0).u.l.pos[0] = GetTrkEndPos( trk, 1-ep );
		InfoMessage( _("Drag to change track length") );

	case C_MOVE:
		d = FindDistance( tempSegs(0).u.l.pos[0], pos );
		valid = TRUE;
		if ( d <= minLength ) {
			if (action == C_MOVE) {
				ErrorMessage( MSG_TRK_TOO_SHORT, _("Straight "), PutDim(fabs(minLength-d)) );
			}
			valid = FALSE;
			return C_CONTINUE;
		}
		Translate( &tempSegs(0).u.l.pos[1], tempSegs(0).u.l.pos[0], GetTrkEndAngle( trk,
		                ep ), d );
		if (action == C_MOVE)
			InfoMessage( _("Straight: Length=%s Angle=%0.3f"),
			             FormatDistance( d ), PutAngle( GetTrkEndAngle( trk, ep ) ) );
		return C_CONTINUE;

	case C_UP:
		if (valid) {
			AdjustStraightEndPt( trk, ep, tempSegs(0).u.l.pos[1] );
		}
		DYNARR_RESET( trkSeg_t, tempSegs_da );
		DrawNewTrack( trk );
		return C_TERMINATE;

	default:
		;
	}
	return C_ERROR;
}


static DIST_T GetLengthStraight( track_p trk )
{
	return FindDistance( GetTrkEndPos(trk,0), GetTrkEndPos(trk,1) );
}


static BOOL_T GetParamsStraight( int inx, track_p trk, coOrd pos,
                                 trackParams_t * params )
{
	params->type = curveTypeStraight;
	if ( inx == PARAMS_NODES ) { return FALSE; }
	if ((inx == PARAMS_CORNU) || (inx == PARAMS_1ST_JOIN)
	    || (inx == PARAMS_2ND_JOIN) ) {
		params->ep = PickEndPoint( pos, trk);
		params->arcP = zero;
		params->arcR = 0.0;
	} else {
		params->ep = PickUnconnectedEndPointSilent( pos, trk );
	}
	if (params->ep == -1) {
		return FALSE;
	}
	params->lineOrig = GetTrkEndPos(trk,1-params->ep);
	params->lineEnd = GetTrkEndPos(trk,params->ep);
	params->len = FindDistance( params->lineOrig, params->lineEnd );
	params->track_angle = FindAngle( params->lineOrig, params->lineEnd);
	params->angle = params->track_angle;
	params->arcR = 0.0;
	return TRUE;
}


static BOOL_T MoveEndPtStraight( track_p *trk, EPINX_T *ep, coOrd pos,
                                 DIST_T d0 )
{
	if ( NormalizeAngle( FindAngle( GetTrkEndPos(*trk,1-*ep), pos ) -
	                     GetTrkEndAngle(*trk,*ep) + 0.5 ) > 1.0 ) {
		ErrorMessage( MSG_MOVED_BEYOND_END_TRK );
		return FALSE;
	}
	Translate( &pos, pos, GetTrkEndAngle(*trk,*ep)+180, d0 );
	AdjustStraightEndPt( *trk, *ep, pos );
	return TRUE;
}


static BOOL_T QueryStraight( track_p trk, int query )
{
	switch ( query ) {
	case Q_CAN_PARALLEL:
	case Q_CAN_MODIFYRADIUS:
	case Q_CAN_GROUP:
	case Q_ISTRACK:
	case Q_CORNU_CAN_MODIFY:
	case Q_MODIFY_CAN_SPLIT:
	case Q_CAN_EXTEND:
	case Q_HAS_DESC:
		return TRUE;
	default:
		return FALSE;
	}
}


static void FlipStraight(
        track_p trk,
        coOrd orig,
        ANGLE_T angle )
{
	ComputeBoundingBox( trk );
}


static BOOL_T MakeParallelStraight(
        track_p trk,
        coOrd pos,
        DIST_T sep,
        DIST_T factor,
        track_p * newTrkR,
        coOrd * p0R,
        coOrd * p1R,
        BOOL_T track)
{
	ANGLE_T angle = GetTrkEndAngle(trk,1);
	coOrd p0, p1;
	if ( NormalizeAngle( FindAngle( GetTrkEndPos(trk,0), pos ) - GetTrkEndAngle(trk,
	                     1) ) < 180.0 ) {
		angle += 90;
	} else {
		angle -= 90;
	}
	Translate( &p0, GetTrkEndPos(trk,0), angle, sep );
	Translate( &p1, GetTrkEndPos(trk,1), angle, sep );
	if ( newTrkR ) {
		if (track) {
			*newTrkR = NewStraightTrack( p0, p1 );
		} else {
			DYNARR_SET( trkSeg_t, tempSegs_da, 1 );
			tempSegs(0).color = wDrawColorBlack;
			tempSegs(0).lineWidth = 0;
			tempSegs(0).type = SEG_STRLIN;
			tempSegs(0).u.l.pos[0] = p0;
			tempSegs(0).u.l.pos[1] = p1;
			*newTrkR = MakeDrawFromSeg( zero, 0.0, &tempSegs(0) );
			SetTrkBits( *newTrkR, TB_HIDEDESC );
		}

	} else {
		DYNARR_SET( trkSeg_t, tempSegs_da, 1 );
		tempSegs(0).color = wDrawColorBlack;
		tempSegs(0).lineWidth = 0;
		tempSegs(0).type = track?SEG_STRTRK:SEG_STRLIN;
		tempSegs(0).u.l.pos[0] = p0;
		tempSegs(0).u.l.pos[1] = p1;
	}
	if ( p0R ) { *p0R = p0; }
	if ( p1R ) { *p1R = p1; }
	return TRUE;
}


static wBool_t CompareStraight( track_cp trk1, track_cp trk2 )
{
	return TRUE;
}


static trackCmd_t straightCmds = {
	"STRAIGHT",
	DrawStraight,
	DistanceStraight,
	DescribeStraight,
	DeleteStraight,
	WriteStraight,
	ReadStraight,
	MoveStraight,
	RotateStraight,
	RescaleStraight,
	AuditStraight,
	GetAngleStraight,
	SplitStraight,
	TraverseStraight,
	EnumerateStraight,
	NULL,			/* redraw */
	TrimStraight,
	ExtendStraightToJoin,
	ModifyStraight,
	GetLengthStraight,
	GetParamsStraight,
	MoveEndPtStraight,
	QueryStraight,
	NULL,			/* ungroup */
	FlipStraight,
	NULL,
	NULL,
	NULL,
	MakeParallelStraight,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	CompareStraight
};

EXPORT void StraightSegProc(
        segProc_e cmd,
        trkSeg_p segPtr,
        segProcData_p data )
{
	ANGLE_T a0, a1, a2;
	DIST_T d, d0, d1;
	coOrd p0, p1;

	switch( cmd ) {

	case SEGPROC_TRAVERSE1:
		a1 = FindAngle( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		a2 = NormalizeAngle( a1-data->traverse1.angle );
		data->traverse1.backwards = ((a2 < 270) && (a2 > 90));
		data->traverse1.dist = FindDistance( segPtr->u.l.pos[data->traverse1.backwards
		                                     ?1:0], data->traverse1.pos );
		data->traverse1.reverse_seg = FALSE;
		data->traverse1.negative = FALSE;
		data->traverse1.segs_backwards = FALSE;
		data->traverse1.BezSegInx = 0;
		break;

	case SEGPROC_TRAVERSE2:
		d = FindDistance( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		if ( d >= data->traverse2.dist ) {
			a1 = FindAngle( segPtr->u.l.pos[data->traverse2.segDir],
			                segPtr->u.l.pos[1-data->traverse2.segDir] );
			Translate( &data->traverse2.pos, segPtr->u.l.pos[data->traverse2.segDir], a1,
			           data->traverse2.dist );
			data->traverse2.dist = 0;
			data->traverse2.angle = a1;
		} else {
			a1 = FindAngle( segPtr->u.l.pos[data->traverse2.segDir],
			                segPtr->u.l.pos[1-data->traverse2.segDir] );
			Translate( &data->traverse2.pos, segPtr->u.l.pos[data->traverse2.segDir], a1,
			           d );
			data->traverse2.dist -= d;
			data->traverse2.angle = a1;
		}
		break;

	case SEGPROC_DRAWROADBEDSIDE:
		d0 = FindDistance( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		a0 = FindAngle( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		d1 = d0*data->drawRoadbedSide.first/32.0;
		Translate( &p0, segPtr->u.l.pos[0], a0, d1 );
		Translate( &p0, p0, a0+data->drawRoadbedSide.side,
		           data->drawRoadbedSide.roadbedWidth/2.0 );
		d1 = d0*data->drawRoadbedSide.last/32.0;
		Translate( &p1, segPtr->u.l.pos[0], a0, d1 );
		Translate( &p1, p1, a0+data->drawRoadbedSide.side,
		           data->drawRoadbedSide.roadbedWidth/2.0 );
		REORIGIN1( p0, data->drawRoadbedSide.angle, data->drawRoadbedSide.orig );
		REORIGIN1( p1, data->drawRoadbedSide.angle, data->drawRoadbedSide.orig );
		DrawLine( data->drawRoadbedSide.d, p0, p1, data->drawRoadbedSide.rbw,
		          data->drawRoadbedSide.color );
		break;

	case SEGPROC_DISTANCE:
		data->distance.dd = LineDistance( &data->distance.pos1, segPtr->u.l.pos[0],
		                                  segPtr->u.l.pos[1] );
		break;

	case SEGPROC_FLIP:
		p0 = segPtr->u.l.pos[0];
		segPtr->u.l.pos[0] = segPtr->u.l.pos[1];
		segPtr->u.l.pos[1] = p0;
		break;

	case SEGPROC_NEWTRACK:
		data->newTrack.trk = NewStraightTrack( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		data->newTrack.ep[0] = 0;
		data->newTrack.ep[1] = 1;
		break;

	case SEGPROC_LENGTH:
		data->length.length = FindDistance( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		break;

	case SEGPROC_SPLIT:
		d = FindDistance( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		data->split.length[0] = FindDistance( segPtr->u.l.pos[0], data->split.pos );
		if ( data->split.length[0] <= d ) {
			data->split.length[1] = d-data->split.length[0];
		} else {
			data->split.length[0] = d;
			data->split.length[1] = 0.0;
		}
		Translate( &p0, segPtr->u.l.pos[0], FindAngle( segPtr->u.l.pos[0],
		                segPtr->u.l.pos[1] ), data->split.length[0] );
		data->split.newSeg[0] = *segPtr;
		data->split.newSeg[1] = *segPtr;
		data->split.newSeg[0].u.l.pos[1] = data->split.newSeg[1].u.l.pos[0] = p0;
		break;
	/*
	 * Note GetAngle always gives a positive angle because p0 is always left of p1
	 */
	case SEGPROC_GETANGLE:
		data->getAngle.angle = FindAngle( segPtr->u.l.pos[0], segPtr->u.l.pos[1] );
		data->getAngle.radius = 0.0;
		break;
	}
}


/****************************************
 *
 * GRAPHICS EDITING
 *
 */




track_p NewStraightTrack( coOrd p0, coOrd p1 )
{
	track_p t;
	ANGLE_T a;
	t = NewTrack( 0, T_STRAIGHT, 2, sizeof (struct extraDataStraight_t) );
	// *new-layer* SetTrkScale( t, GetLayoutCurScale() );
	a = FindAngle( p1, p0 );
	SetTrkEndPoint( t, 0, p0, a );
	SetTrkEndPoint( t, 1, p1, NormalizeAngle( a+180.0 ) );
	ComputeBoundingBox( t );
	CheckTrackLength( t );
	SetTrkBits( t, TB_HIDEDESC );
	return t;
}




void InitTrkStraight( void )
{
	T_STRAIGHT = InitObject( &straightCmds );
}
