/** \file tcornu.c
 *
 * CORNU SPIRAL TRACK
 *
 * A Cornu is a spiral arc defined by a polynomial that has the property
 * that curvature varies linearly with distance along the curve. It is a family
 * of curves that include Euler spirals.
 *
 * In order to be useful in XtrkCAD it is defined as a set of Bezier curves each of
 * which is defined as a set of circular arcs and lines.
 *
 * The derivation of the Beziers is done by the Cornu library which must be recalled
 * whenever a change is made in the end conditions.
 *
 * A cornu has a minimum radius and a maximum rate of change of radius.
 *
 * Acknowledgment is given to Dr. Raph Levien whose seminal PhD work on Cornu curves and
 * generous open-sourcing of his libraries both inspired and powers this function.
 *
 *
 *  XTrkCad - Model Railroad CAD
 *
 *  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 "track.h"
#include "draw.h"
#include "cbezier.h"
#include "tbezier.h"
#include "tcornu.h"
#include "ccornu.h"
#include "ccurve.h"
#include "cstraigh.h"
#include "cjoin.h"
#include "common.h"
#include "param.h"
#include "cundo.h"
#include "layout.h"
#include "fileio.h"

EXPORT TRKTYP_T T_CORNU = -1;

static int log_cornu = 0;

static DIST_T GetLengthCornu( track_p );

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

/*
 * Run after any changes to the Cornu points
 */
void SetUpCornuParmFromTracks(track_p trk[2],cornuParm_t * cp,
                              struct extraDataCornu_t* xx)
{
	if (!trk[0]) {
		cp->center[0] = xx->c[0];
		cp->angle[0] = xx->a[0];
		cp->radius[0] = xx->r[0];
	}
	if (!trk[1]) {
		cp->center[1] = xx->c[1];
		cp->angle[1] = xx->a[1];
		cp->radius[1] = xx->r[1];
	}
}

EXPORT BOOL_T FixUpCornu(coOrd pos[4], track_p trk[2], EPINX_T ep[2],
                         struct extraDataCornu_t* xx)
{

	cornuParm_t cp;

	SetUpCornuParmFromTracks(trk,&cp,xx);

	if (!CallCornu(pos, trk, ep, &xx->arcSegs, &cp)) { return FALSE; }

	xx->r[0] = cp.radius[0];
	if (cp.radius[0]==0) {
		xx->a[0] = cp.angle[0];
	} else {
		xx->c[0] = cp.center[0];
	}
	xx->r[1] = cp.radius[1];
	if (cp.radius[1]==0) {
		xx->a[1] = cp.angle[1];
	} else {
		xx->c[1] = cp.center[1];
	}

	xx->minCurveRadius = CornuMinRadius(pos,xx->arcSegs);
	xx->windingAngle = CornuTotalWindingArc(pos,xx->arcSegs);
	DIST_T last_c;
	if (xx->r[0] == 0) { last_c = 0; }
	else { last_c = 1/xx->r[0]; }
	xx->maxRateofChange = CornuMaxRateofChangeofCurvature(pos,xx->arcSegs,&last_c);
	xx->length = CornuLength(pos, xx->arcSegs);
	return TRUE;
}

EXPORT BOOL_T FixUpCornu0(coOrd pos[4],coOrd center[2],ANGLE_T angle[2],
                          DIST_T radius[2],struct extraDataCornu_t* xx)
{
	DIST_T last_c;
	if (!CallCornu0(pos, center, angle, radius,&xx->arcSegs,FALSE)) { return FALSE; }
	xx->minCurveRadius = CornuMinRadius(pos,
	                                    xx->arcSegs);
	if (xx->r[0] == 0) { last_c = 0; }
	else { last_c = 1/xx->r[0]; }
	xx->maxRateofChange = CornuMaxRateofChangeofCurvature(pos,xx->arcSegs,&last_c);
	xx->length = CornuLength(pos, xx->arcSegs);
	xx->windingAngle = CornuTotalWindingArc(pos,xx->arcSegs);
	return TRUE;
}

EXPORT char * CreateSegPathList(track_p trk)
{
	char * cp = "\0\0";
	if (GetTrkType(trk) != T_CORNU) { return cp; }
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	if (xx->cornuPath) { MyFree(xx->cornuPath); }
	xx->cornuPath = MyMalloc(xx->arcSegs.cnt+2);
	int j= 0;
	for (int i = 0; i<xx->arcSegs.cnt; i++,j++) {
		xx->cornuPath[j] = i+1;
	}
	xx->cornuPath[j] = cp[0];
	xx->cornuPath[j+1] = cp[0];
	return xx->cornuPath;
}


#if 0
static void GetCornuAngles( ANGLE_T *a0, ANGLE_T *a1, track_p trk )
{
	CHECK( trk != NULL );

	*a0 = NormalizeAngle( GetTrkEndAngle(trk,0) );
	*a1 = NormalizeAngle( GetTrkEndAngle(trk,1)  );

	LOG( log_cornu, 4, ( "getCornuAngles: = %0.3f %0.3f\n", *a0, *a1 ) )
}
#endif


static void ComputeCornuBoundingBox( track_p trk, struct extraDataCornu_t * xx )
{
	coOrd orig, size;

	GetSegBounds(zero,0,xx->arcSegs.cnt,&DYNARR_N(trkSeg_t,xx->arcSegs,0), &orig,
	             &size);

	coOrd hi, lo;

	lo.x = orig.x;
	lo.y = orig.y;
	hi.x = orig.x+size.x;
	hi.y = orig.y+size.y;

	SetBoundingBox( trk, hi, lo );
}


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

	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	if (( GetTrkBits( trk ) & TB_HIDEDESC ) != 0) { xx->descriptionOff = zero; }

	coOrd end0, end0off, end1, end1off;
	end0 = xx->pos[0];
	end1 = xx->pos[1];
	ANGLE_T a;
	a = FindAngle(end0,end1);
	Translate(&end0off,end0,a+90,xx->descriptionOff.y);
	Translate(&end1off,end1,a+90,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 (DistanceCornu(trk,&tpos)<FindDistance( p1, pos )) {
		return DistanceCornu(trk,&pos);
	}
	return FindDistance( p1, pos );
}

typedef struct {
	coOrd pos;
	ANGLE_T angle;
} pos_angle_t;

static void DrawCornuDescription(
        track_p trk,
        drawCmd_p d,
        wDrawColor color )
{
	coOrd epos0, epos1, offpos0, offpos1;

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

	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	epos0 = xx->pos[0];
	epos1 = xx->pos[1];
	ANGLE_T a = FindAngle(epos0,epos1);
	Translate(&offpos0,epos0,a+90,xx->descriptionOff.y);
	Translate(&offpos1,epos1,a+90,xx->descriptionOff.y);

	wStandardFont( F_TIMES, FALSE, FALSE );

	sprintf( message, _("Cornu: L %s A %0.3f L %s MinR %s"),
	         FormatDistance(FindDistance(xx->pos[0], xx->pos[1])),
	         FindAngle(xx->pos[0], xx->pos[1]),
	         FormatDistance(xx->length),
	         FormatDistance((xx->minCurveRadius>=DIST_INF)?0.0:xx->minCurveRadius));
	DrawLine(d,xx->pos[0],offpos0,0,color);
	DrawLine(d,xx->pos[1],offpos1,0,color);
	DrawDimLine( d, offpos0, offpos1, message, (wFontSize_t)descriptionFontSize,
	             xx->descriptionOff.x+0.5, 0, color, 0x00 );

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

		AddTrkDetails(d, trk, details_pos, xx->length, color);
	}

}


STATUS_T CornuDescriptionMove(
        track_p trk,
        wAction_t action,
        coOrd pos )
{
//	static coOrd p0,p1;
//	static BOOL_T editState;

	if (GetTrkType(trk) != T_CORNU) { return C_CONTINUE; }

	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	ANGLE_T ap;
	coOrd end0, end1;
	end0 = xx->pos[0];
	end1 = xx->pos[1];
	ap = NormalizeAngle(FindAngle(end0,pos)-FindAngle(end0,end1));

	xx->descriptionOff.y = FindDistance(end0,pos)*sin(D2R(ap));
	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;
}

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

static struct {
	coOrd pos[2];
	ANGLE_T angle[2];
	DIST_T radius[2];
	coOrd center[2];
	FLOAT_T elev[2];
	FLOAT_T length;
	FLOAT_T grade;
	DIST_T minRadius;
	DIST_T maxRateOfChange;
	ANGLE_T windingAngle;
	unsigned int layerNumber;
	dynArr_t segs;
	long lineWidth;
	wDrawColor color;
} cornData;

typedef enum { P0, A0, R0, C0, Z0, P1, A1, R1, C1, Z1, RA, RR, WA, LN, GR, LY } cornuDesc_e;
static descData_t cornuDesc[] = {
	/*P0*/	{ DESC_POS, N_("End Pt 1: X,Y"), &cornData.pos[0] },
	/*A0*/  { DESC_ANGLE, N_("End Angle"), &cornData.angle[0] },
	/*R0*/  { DESC_DIM, N_("Radius "), &cornData.radius[0] },
	/*C0*/	{ DESC_POS, N_("Center X,Y"), &cornData.center[0] },
	/*Z0*/	{ DESC_DIM, N_("Z1"), &cornData.elev[0] },
	/*P1*/	{ DESC_POS, N_("End Pt 2: X,Y"), &cornData.pos[1] },
	/*A1*/  { DESC_ANGLE, N_("End Angle"), &cornData.angle[1] },
	/*R1*/  { DESC_DIM, N_("Radius"), &cornData.radius[1] },
	/*C1*/	{ DESC_POS, N_("Center X,Y"), &cornData.center[1] },
	/*Z1*/	{ DESC_DIM, N_("Z2"), &cornData.elev[1] },
	/*RA*/	{ DESC_DIM, N_("Minimum Radius"), &cornData.minRadius },
	/*RR*/  { DESC_FLOAT, N_("Max Rate Of Curve Change/Scale"), &cornData.maxRateOfChange },
	/*WA*/  { DESC_ANGLE, N_("Total Winding Angle"), &cornData.windingAngle },
	/*LN*/	{ DESC_DIM, N_("Length"), &cornData.length },
	/*GR*/	{ DESC_FLOAT, N_("Grade"), &cornData.grade },
	/*LY*/	{ DESC_LAYER, N_("Layer"), &cornData.layerNumber },
	{ DESC_NULL }
};


static void UpdateCornu( track_p trk, int inx, descData_p descUpd,
                         BOOL_T final )
{
	BOOL_T updateEndPts;
	EPINX_T ep;

	cornuParm_t cp;
	if ( inx == -1 ) {
		return;
	}
	updateEndPts = FALSE;
	UndrawNewTrack(trk);
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	switch ( inx ) {
	case P0:
		if (GetTrkEndTrk(trk,0)) { break; }
		updateEndPts = TRUE;
		xx->pos[0] = cornData.pos[0];
		Translate(&xx->c[0],xx->pos[0],xx->a[0]+90,xx->r[0]);
		cornData.center[0] = xx->c[0];
		cornuDesc[P0].mode |= DESC_CHANGE;
		cornuDesc[C0].mode |= DESC_CHANGE;
	/* no break */
	case P1:
		if (GetTrkEndTrk(trk,1)) { break; }
		updateEndPts = TRUE;
		xx->pos[1]= cornData.pos[1];
		Translate(&xx->c[1],xx->pos[1],xx->a[1]-90,xx->r[1]);
		cornData.center[1] = xx->c[1];
		cornuDesc[P1].mode |= DESC_CHANGE;
		cornuDesc[C1].mode |= DESC_CHANGE;
		break;
	case A0:
		if (GetTrkEndTrk(trk,0)) { break; }
		updateEndPts = TRUE;
		xx->a[0] = cornData.angle[0];
		Translate(&xx->c[0],xx->pos[0],xx->a[0]+90,xx->r[0]);
		cornData.center[0] = xx->c[0];
		cornuDesc[A0].mode |= DESC_CHANGE;
		cornuDesc[C0].mode |= DESC_CHANGE;
		break;
	case A1:
		if (GetTrkEndTrk(trk,1)) { break; }
		updateEndPts = TRUE;
		xx->a[1]= cornData.angle[1];
		Translate(&xx->c[1],xx->pos[1],xx->a[1]-90,xx->r[1]);
		cornData.center[1] = xx->c[1];
		cornuDesc[A1].mode |= DESC_CHANGE;
		cornuDesc[C1].mode |= DESC_CHANGE;
		break;
	case C0:
		if (GetTrkEndTrk(trk,0)) { break; }
		//updateEndPts = TRUE;
		//xx->c[0] = cornData.center[0];
		//cornuDesc[C0].mode |= DESC_CHANGE;
		break;
	case C1:
		if (GetTrkEndTrk(trk,1)) { break; }
		//updateEndPts = TRUE;
		//xx->c[1] = cornData.center[1];
		//cornuDesc[C1].mode |= DESC_CHANGE;
		break;
	case R0:
		if (GetTrkEndTrk(trk,0)) { break; }
		updateEndPts = TRUE;
		xx->r[0] = fabs(cornData.radius[0]);
		Translate(&xx->c[0],xx->pos[0],NormalizeAngle(xx->a[0]+90),cornData.radius[0]);
		cornData.center[0] = xx->c[0];
		cornData.radius[0] = fabs(cornData.radius[0]);
		cornuDesc[R0].mode |= DESC_CHANGE;
		cornuDesc[C0].mode |= DESC_CHANGE;
		break;
	case R1:
		if (GetTrkEndTrk(trk,1)) { break; }
		updateEndPts = TRUE;
		xx->r[1]= fabs(cornData.radius[1]);
		Translate(&xx->c[1],xx->pos[1],NormalizeAngle(xx->a[1]-90),cornData.radius[1]);
		cornData.center[1] = xx->c[1];
		cornData.radius[1] = fabs(cornData.radius[1]);
		cornuDesc[R1].mode |= DESC_CHANGE;
		cornuDesc[C1].mode |= DESC_CHANGE;
		break;
	case Z0:
	case Z1:
		ep = (inx==Z0?0:1);
		UpdateTrkEndElev( trk, ep, GetTrkEndElevUnmaskedMode(trk,ep), cornData.elev[ep],
		                  NULL );
		ComputeElev( trk, 1-ep, FALSE, &cornData.elev[1-ep], NULL, TRUE );
		if ( cornData.length > minLength ) {
			cornData.grade = fabs( (cornData.elev[0]-cornData.elev[1])/cornData.length )
			                 *100.0;
		} else {
			cornData.grade = 0.0;
		}
		cornuDesc[GR].mode |= DESC_CHANGE;
		cornuDesc[inx==Z0?Z1:Z0].mode |= DESC_CHANGE;
		return;
	case LY:
		SetTrkLayer( trk, cornData.layerNumber);
		break;
	default:
		CHECKMSG( FALSE, ( "updateCornu: Bad inx %d", inx ) );
	}
//	track_p tracks[2];
//	tracks[0] = GetTrkEndTrk(trk,0);
//	tracks[1] = GetTrkEndTrk(trk,1);

	if (updateEndPts) {
		if ( GetTrkEndTrk(trk,0) == NULL ) {
			SetTrkEndPoint( trk, 0, cornData.pos[0], xx->a[0]);
			cornuDesc[A0].mode |= DESC_CHANGE;
		}
		if ( GetTrkEndTrk(trk,1) == NULL ) {
			SetTrkEndPoint( trk, 1, cornData.pos[1], xx->a[1]);
			cornuDesc[A1].mode |= DESC_CHANGE;
		}
	}

	track_p ts[2];
	ts[0] = GetTrkEndTrk(trk,0);
	ts[1] = GetTrkEndTrk(trk,1);
	SetUpCornuParmFromTracks(ts,&cp,xx);
	CallCornu0(xx->pos, xx->c, xx->a, xx->r, &xx->arcSegs, FALSE);

	//FixUpCornu(xx->bezierData.pos, xx, IsTrack(trk));
	ComputeCornuBoundingBox(trk, xx);
	DrawNewTrack( trk );
}


static void DescribeCornu( track_p trk, char * str, CSIZE_T len )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	DIST_T d;

	d = xx->length;
	sprintf( str,
	         _("Cornu Track(%d): Layer=%u MinRadius=%s Length=%s EP=[%0.3f,%0.3f] [%0.3f,%0.3f]"),
	         GetTrkIndex(trk),
	         GetTrkLayer(trk)+1,
	         FormatDistance(xx->minCurveRadius),
	         FormatDistance(d),
	         PutDim(xx->pos[0].x),PutDim(xx->pos[0].y),
	         PutDim(xx->pos[1].x),PutDim(xx->pos[1].y)
	       );

	cornData.length = xx->length;
	cornData.minRadius = xx->minCurveRadius;
	cornData.maxRateOfChange = xx->maxRateofChange*GetScaleRatio(
	                                   GetLayoutCurScale());
	cornData.windingAngle = xx->windingAngle;
	cornData.layerNumber = GetTrkLayer(trk);
	cornData.pos[0] = xx->pos[0];
	cornData.pos[1] = xx->pos[1];
	cornData.angle[0] = xx->a[0];
	cornData.angle[1] = xx->a[1];
	cornData.center[0] = xx->c[0];
	cornData.center[1] = xx->c[1];
	cornData.radius[0] = xx->r[0];
	cornData.radius[1] = xx->r[1];
	if (GetTrkType(trk) == T_CORNU) {
		ComputeElev( trk, 0, FALSE, &cornData.elev[0], NULL, FALSE );
		ComputeElev( trk, 1, FALSE, &cornData.elev[1], NULL, FALSE );

		if ( cornData.length > minLength ) {
			cornData.grade = fabs( (cornData.elev[0]-cornData.elev[1])/cornData.length )
			                 *100.0;
		} else {
			cornData.grade = 0.0;
		}
	}
	BOOL_T trk0 = (GetTrkEndTrk(trk,0)!=NULL);
	BOOL_T trk1 = (GetTrkEndTrk(trk,1)!=NULL);

	cornuDesc[P0].mode = !trk0?0:DESC_RO;
	cornuDesc[P1].mode = !trk1?0:DESC_RO;
	cornuDesc[LN].mode = DESC_RO;
	cornuDesc[Z0].mode = EndPtIsDefinedElev(trk,0)?0:DESC_RO;
	cornuDesc[Z1].mode = EndPtIsDefinedElev(trk,1)?0:DESC_RO;


	cornuDesc[A0].mode = !trk0?0:DESC_RO;
	cornuDesc[A1].mode = !trk1?0:DESC_RO;
	cornuDesc[C0].mode = DESC_RO;
	cornuDesc[C1].mode = DESC_RO;
	cornuDesc[R0].mode = !trk0?0:DESC_RO;
	cornuDesc[R1].mode = !trk1?0:DESC_RO;
	cornuDesc[GR].mode = DESC_RO;
	cornuDesc[RA].mode = DESC_RO;
	cornuDesc[RR].mode = DESC_RO;
	cornuDesc[WA].mode = DESC_RO;
	cornuDesc[LY].mode = DESC_NOREDRAW;

	DoDescribe( _("Cornu Track"), trk, cornuDesc, UpdateCornu );

}


DIST_T DistanceCornu( track_p t, coOrd * p )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t);
	//return BezierMathDistance(p,xx->bezierData.pos,100, &s);

	DIST_T d = DIST_INF;
	coOrd p2 = xx->pos[0];    //Set initial point
	segProcData_t segProcData;
	for (int i = 0; i<xx->arcSegs.cnt; i++) {
		trkSeg_t seg = DYNARR_N(trkSeg_t,xx->arcSegs,i);
		if (seg.type == SEG_FILCRCL) { continue; }
		segProcData.distance.pos1 = * p;
		SegProc(SEGPROC_DISTANCE,&seg,&segProcData);
		if (segProcData.distance.dd<d) {
			d = segProcData.distance.dd;
			p2 = segProcData.distance.pos1;
		}
	}
	//d = BezierDistance( p, xx->bezierData.pos[0], xx->bezierData.pos[1], xx->bezierData.pos[2], xx->bezierData.pos[1], 100, NULL );
	* p = p2;
	return d;
}

static void DrawCornu( track_p t, drawCmd_p d, wDrawColor color )
{
	long widthOptions = DTS_LEFT|DTS_RIGHT;

	if ( ((d->options&DC_SIMPLE)==0) &&
	     (labelWhen == 2 || (labelWhen == 1 && (d->options&DC_PRINT))) &&
	     labelScale >= d->scale &&
	     ( GetTrkBits( t ) & TB_HIDEDESC ) == 0 ) {
		DrawCornuDescription( t, d, color );
	}
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t);
	DrawSegsDA(d,t,zero,0.0,&xx->arcSegs, GetTrkGauge(t), color, widthOptions);
	DrawEndPt( d, t, 0, color );
	DrawEndPt( d, t, 1, color );
}

void FreeSubSegs(trkSeg_t* s)
{
	if (s->type == SEG_BEZTRK || s->type == SEG_BEZLIN) {
		DYNARR_FREE( trkSeg_t, s->bezSegs );
	}
}

static void DeleteCornu( track_p t )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t);

	for (int i=0; i<xx->arcSegs.cnt; i++) {
		trkSeg_t s = DYNARR_N(trkSeg_t,xx->arcSegs,i);
		FreeSubSegs(&s);
	}

	DYNARR_FREE( trkSeg_t, xx->arcSegs );
}

static BOOL_T WriteCornu( track_p t, FILE * f )
{
	int bits;
	long options;
	BOOL_T rc = TRUE;
	BOOL_T track =(GetTrkType(t)==T_CORNU);
	options = GetTrkWidth(t) & 0x0F;
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t);
	if ( ( GetTrkBits(t) & TB_HIDEDESC ) == 0 ) { options |= 0x80; }
	bits = GetTrkVisible(t)|(GetTrkNoTies(t)?1<<2:0)|(GetTrkBridge(t)?1<<3:0)|
	       (GetTrkRoadbed(t)?1<<4:0);
	rc &= fprintf(f,
	              "%s %d %d %ld 0 0 %s %d %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f %0.6f \n",
	              "CORNU",GetTrkIndex(t), GetTrkLayer(t), (long)options,
	              GetTrkScaleName(t), bits,
	              xx->pos[0].x, xx->pos[0].y,
	              xx->a[0],
	              xx->r[0],
	              xx->c[0].x,xx->c[0].y,
	              xx->pos[1].x, xx->pos[1].y,
	              xx->a[1],
	              xx->r[1],
	              xx->c[1].x,xx->c[1].y )>0;
	if (track) {
		rc &= WriteEndPt( f, t, 0 );
		rc &= WriteEndPt( f, t, 1 );
	}
	rc &= WriteSegs( f, xx->arcSegs.cnt, &DYNARR_N(trkSeg_t,xx->arcSegs,0) );
	return rc;
}

static BOOL_T ReadCornu( char * line )
{
	struct extraDataCornu_t *xx;
	track_p t;
	wIndex_t index;
	BOOL_T visible;
	DIST_T r0,r1;
	ANGLE_T a0,a1;
	coOrd p0, p1, c0, c1;
	char scale[10];
	wIndex_t layer;
	long options;
//	char * cp = NULL;

	if (!GetArgs( line+6, "dLl00sdpffppffp",
	              &index, &layer, &options, scale, &visible, &p0, &a0, &r0, &c0, &p1, &a1, &r1,
	              &c1 ) ) {
		return FALSE;
	}
	if ( !ReadSegs() ) {
		return FALSE;
	}
	t = NewTrack( index, T_CORNU, 0, sizeof *xx );

	SetTrkVisible(t, visible&2);
	SetTrkNoTies(t, visible&4);
	SetTrkBridge(t, visible&8);
	SetTrkRoadbed(t, visible&16);
	SetTrkScale(t, LookupScale(scale));
	SetTrkLayer(t, layer );
	SetTrkWidth(t, (int)(options&0x0F));
	if ( paramVersion < VERSION_DESCRIPTION2 || ( options & 0x80 ) == 0 ) { SetTrkBits(t,TB_HIDEDESC); }
	xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t);
	xx->pos[0] = p0;
	xx->pos[1] = p1;
	xx->a[0] = a0;
	xx->r[0] = r0;
	xx->a[1] = a1;
	xx->c[0] = c0;
	xx->c[1] = c1;
	xx->r[1] = r1;
	xx->descriptionOff.x = xx->descriptionOff.y = 0.0;
	FixUpCornu0(xx->pos,xx->c,xx->a, xx->r, xx);
	ComputeCornuBoundingBox(t,xx);
	SetEndPts(t,2);
	return TRUE;
}

static void MoveCornu( track_p trk, coOrd orig )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	for (int i=0; i<2; i++) {
		xx->pos[i].x += orig.x;
		xx->pos[i].y += orig.y;
		xx->c[i].x += orig.x;
		xx->c[i].y += orig.y;
	}
	RebuildCornu(trk);
}

static void RotateCornu( track_p trk, coOrd orig, ANGLE_T angle )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	for (int i=0; i<2; i++) {
		Rotate( &xx->pos[i], orig, angle );
		Rotate( &xx->c[i], orig, angle);
		xx->a[i] = NormalizeAngle(xx->a[i]+angle);
	}
	RebuildCornu(trk);
}

static void RescaleCornu( track_p trk, FLOAT_T ratio )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	for (int i=0; i<2; i++) {
		xx->pos[i].x *= ratio;
		xx->pos[i].y *= ratio;
		xx->c[i].x *= ratio;
		xx->c[i].y *= ratio;
		xx->r[i] *= ratio;
	}
	RebuildCornu(trk);

}

EXPORT BOOL_T SetCornuEndPt(track_p trk, EPINX_T inx, coOrd pos, coOrd center,
                            ANGLE_T angle, DIST_T radius)
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	xx->pos[inx] = pos;
	xx->c[inx] = center;
	xx->a[inx] = angle;
	xx->r[inx] = radius;
	if (!RebuildCornu(trk)) { return FALSE; }
	SetTrkEndPoint( trk, inx, xx->pos[inx], xx->a[inx]);
	return TRUE;
}


void GetCornuParmsNear(track_p t, int sel, coOrd * pos2, coOrd * center,
                       ANGLE_T * angle2,  DIST_T * radius )
{
	coOrd pos = *pos2;
//	double dd = DistanceCornu(t, &pos);   //Pos adjusted to be on curve
	int inx;
	*radius = 0.0;
	*angle2 = 0.0;
	*center = zero;
	wBool_t back,neg;
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(t, T_CORNU, extraDataCornu_t);
	ANGLE_T angle = GetAngleSegs(xx->arcSegs.cnt,&DYNARR_N(trkSeg_t,xx->arcSegs,0),
	                             &pos,&inx,NULL,&back,NULL,&neg);

	if (inx == -1) {
		return;    //Error in GetAngle
	}

	trkSeg_p segPtr = &DYNARR_N(trkSeg_t, xx->arcSegs, inx);

	if (segPtr->type == SEG_BEZTRK) {
		GetAngleSegs(segPtr->bezSegs.cnt,&DYNARR_N(trkSeg_t,segPtr->bezSegs,0),&pos,
		             &inx,NULL,&back,NULL,&neg);
		if (inx ==-1) { return; }
		segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, inx);
	}

	if (segPtr->type == SEG_STRTRK) {
		*radius = 0.0;
		*center = zero;
	} else if (segPtr->type == SEG_CRVTRK) {
		*center = segPtr->u.c.center;
		*radius = fabs(segPtr->u.c.radius);
	}
	if (sel) {
		angle = NormalizeAngle(angle+(neg==back?0:180));
	} else {
		angle = NormalizeAngle(angle+(neg==back?180:0));
	}
	*angle2 = angle;
	*pos2 = pos;
}

void GetCornuParmsTemp(dynArr_t * array_p, int sel, coOrd * pos2,
                       coOrd * center, ANGLE_T * angle2,  DIST_T * radius )
{

	coOrd pos = *pos2;
	int inx;
	wBool_t back,neg;
	*radius = 0.0;
	*center = zero;
	*angle2 = 0.0;

	ANGLE_T angle = GetAngleSegs(array_p->cnt,&DYNARR_N(trkSeg_t,*array_p,0),&pos,
	                             &inx,NULL,&back,NULL,&neg);

	if (inx==-1) { return; }

	trkSeg_p segPtr = &DYNARR_N(trkSeg_t, *array_p, inx);

	if (segPtr->type == SEG_BEZTRK) {

		GetAngleSegs(segPtr->bezSegs.cnt,&DYNARR_N(trkSeg_t,segPtr->bezSegs,0),&pos,
		             &inx,NULL,&back,NULL,&neg);

		if (inx ==-1) { return; }

		segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, inx);

	}

	if (segPtr->type == SEG_STRTRK) {
		*radius = 0.0;
		*center = zero;
	} else if (segPtr->type == SEG_CRVTRK) {
		*center = segPtr->u.c.center;
		*radius = fabs(segPtr->u.c.radius);
	}
	if (sel) {
		angle = NormalizeAngle(angle+(neg==back?0:180));
	} else {
		angle = NormalizeAngle(angle+(neg==back?180:0));
	}
	*angle2 = angle;
	*pos2 = pos;
}


/**
 * Split the Track at approximately the point pos.
 */
static BOOL_T SplitCornu( track_p trk, coOrd pos, EPINX_T ep, track_p *leftover,
                          EPINX_T * ep0, EPINX_T * ep1 )
{
	track_p trk1;
	DIST_T radius = 0.0;
	coOrd center;
	int inx,subinx;
//  BOOL_T track;
//  track = IsTrack(trk);

	cornuParm_t new;

	double dd = DistanceCornu(trk, &pos);
	if (dd>minLength) { return FALSE; }
	BOOL_T back, neg;

	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	ANGLE_T angle = GetAngleSegs(xx->arcSegs.cnt,&DYNARR_N(trkSeg_t,xx->arcSegs,0),
	                             &pos, &inx,NULL,&back,&subinx,&neg);

	if (inx == -1) { return FALSE; }

	trkSeg_p segPtr = &DYNARR_N(trkSeg_t, xx->arcSegs, inx);

	if (subinx != -1) {
		segPtr = &DYNARR_N(trkSeg_t, segPtr->bezSegs, subinx);
	}

	if (segPtr->type == SEG_STRTRK) {
		radius = 0.0;
		center = zero;
	} else if (segPtr->type == SEG_CRVTRK) {
		center = segPtr->u.c.center;
		radius = fabs(segPtr->u.c.radius);
	}
	if (ep) {
		new.pos[0] = pos;
		new.pos[1] = xx->pos[1];
		new.angle[0] = NormalizeAngle(angle+(neg==back?180:0));
		new.angle[1] = xx->a[1];
		new.center[0] = center;
		new.center[1] = xx->c[1];
		new.radius[0] = radius;
		new.radius[1] = xx->r[1];
	} else {
		new.pos[1] = pos;
		new.pos[0] = xx->pos[0];
		new.angle[1] = NormalizeAngle(angle+(neg==back?0:180));
		new.angle[0] = xx->a[0];
		new.center[1] = center;
		new.center[0] = xx->c[0];
		new.radius[1] = radius;
		new.radius[0] = xx->r[0];
	}

	trk1 = NewCornuTrack(new.pos,new.center,new.angle,new.radius,NULL,0);
	//Copy elevation details from old ep to new ep 0/1
	if (trk1==NULL) {
		wBeep();
		InfoMessage(
		        _("Cornu Create Failed for p1[%0.3f,%0.3f] p2[%0.3f,%0.3f], c1[%0.3f,%0.3f] c2[%0.3f,%0.3f], a1=%0.3f a2=%0.3f, r1=%s r2=%s"),
		        new.pos[0].x,new.pos[0].y,
		        new.pos[1].x,new.pos[1].y,
		        new.center[0].x,new.center[0].y,
		        new.center[1].x,new.center[1].y,
		        new.angle[0],new.angle[1],
		        FormatDistance(new.radius[0]),FormatDistance(new.radius[1]));
		UndoEnd();
		return FALSE;
	}
	DIST_T height;
	int opt;
	GetTrkEndElev(trk,ep,&opt,&height);
	UpdateTrkEndElev( trk1, ep, opt, height,
	                  (opt==ELEV_STATION)?GetTrkEndElevStation(trk,ep):NULL );

	UndoModify(trk);
	xx->pos[ep] = pos;
	xx->a[ep] = NormalizeAngle(new.angle[1-ep]+180);
	xx->r[ep] = new.radius[1-ep];
	xx->c[ep] = new.center[1-ep];
	//Wipe out old elevation for ep1

	RebuildCornu(trk);

	SetTrkEndPoint(trk, ep, xx->pos[ep], xx->a[ep]);
	UpdateTrkEndElev( trk, ep, ELEV_NONE, 0, NULL);

	*leftover = trk1;
	*ep0 = 1-ep;    		//Which end is for new on pos?
	*ep1 = ep;		//Which end is for old trk?

	return TRUE;
}

BOOL_T MoveCornuEndPt ( track_p *trk, EPINX_T *ep, coOrd pos, DIST_T d0 )
{
	track_p trk2;
	if (SplitTrack(*trk,pos,*ep,&trk2,TRUE)) {
		if (trk2) {
			UndrawNewTrack( trk2 );
			DeleteTrack(trk2,TRUE);
		}
		struct extraDataCornu_t *xx = GET_EXTRA_DATA(*trk, T_CORNU, extraDataCornu_t);
		SetTrkEndPoint( *trk, *ep, *ep?xx->pos[1]:xx->pos[0], *ep?xx->a[1]:xx->a[0] );
		DrawNewTrack( *trk );
		return TRUE;
	}
	return FALSE;
}
static int log_traverseCornu = 0;
/*
 * TraverseCornu is used to position a train/car.
 * We find a new position and angle given a current pos, angle and a distance to travel.
 *
 * The output can be TRUE -> we have moved the point to a new point or to the start/end of the next track
 * 					FALSE -> we have not found that point because pos was not on/near the track
 *
 * 	If true we supply the remaining distance to go (always positive).
 *  We detect the movement direction by comparing the current angle to the angle of the track at the point.
 *
 *  The entire Cornu may be reversed or forwards depending on the way it was drawn.
 *
 *  Each Bezier segment within that Cornu structure therefore may be processed forwards or in reverse.
 *  So for each segment we call traverse1 to get the direction and extra distance to go to get to the current point
 *  and then use that for traverse2 to actually move to the new point
 *
 *  If we exceed the current point's segment we move on to the next until the end of this track or we have found the spot.
 *
 */
static BOOL_T TraverseCornu( traverseTrack_p trvTrk, DIST_T * distR )
{
	track_p trk = trvTrk->trk;
	DIST_T dist = *distR;
	segProcData_t segProcData;
	BOOL_T cornu_backwards= FALSE;
	BOOL_T neg = FALSE;
	DIST_T d = DIST_INF;
	coOrd pos1 = trvTrk->pos, pos2 = trvTrk->pos;
	ANGLE_T a1,a2;
	int inx, segInx = 0;
	EPINX_T ep;
	BOOL_T back;
	LOG( log_traverseCornu, 1, ( "TravCornu-In [%0.3f %0.3f] A%0.3f D%0.3f \n",
	                             trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR ))
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	trkSeg_p segPtr = &DYNARR_N(trkSeg_t,xx->arcSegs,0);

	a2 = GetAngleSegs(		  						//Find correct Segment and nearest point in it
	             xx->arcSegs.cnt,segPtr,
	             &pos2, &segInx, &d, &back, NULL,
	             &neg);   	//d = how far pos2 from old pos2 = trvTrk->pos

	if ( d > 10 ) {
		ErrorMessage( "traverseCornu: Position is not near track: %0.3f", d );
		return FALSE;   						//This means the input pos is not on or close to the track.
	}
	if (back) { a2 = NormalizeAngle(a2+180); }		    //If reverse segs - reverse angle
	a1 = NormalizeAngle(a2
	                    -trvTrk->angle);			//Establish if we are going fwds or backwards globally
	if (a1<270
	    && a1>90) {							//Must add 180 if the seg is reversed or inverted (but not both)
		cornu_backwards = TRUE;
		ep = 0;
	} else {
		cornu_backwards = FALSE;
		ep = 1;
	}
	if (neg) {
		cornu_backwards = !cornu_backwards;			//Reversed direction
		ep = 1-ep;
	}
	segProcData.traverse1.pos = pos2;					//actual point on curve
	segProcData.traverse1.angle =
	        trvTrk->angle;        //direction car is going for Traverse 1
	LOG( log_traverseCornu, 1, ( "  TravCornu-GetSubA A%0.3f I%d N%d B%d CB%d\n",
	                             a2, segInx, neg, back, cornu_backwards ))
	inx = segInx;
	while (inx >=0 && inx<xx->arcSegs.cnt) {
		segPtr = (trkSeg_p)xx->arcSegs.ptr
		         +inx;  	    //move in to the identified Bezier segment
		SegProc( SEGPROC_TRAVERSE1, segPtr, &segProcData );
		BOOL_T backwards =
		        segProcData.traverse1.backwards;			//do we process this segment backwards?
		BOOL_T reverse_seg = segProcData.traverse1.reverse_seg;		//Info only
		int BezSegInx = segProcData.traverse1.BezSegInx;			//Which subSeg was it?
		BOOL_T segs_backwards = segProcData.traverse1.segs_backwards;

		dist += segProcData.traverse1.dist;						//Add in the part of the Bezier to get to pos

		segProcData.traverse2.dist = dist;						//Set up Traverse2
		segProcData.traverse2.segDir = backwards;
		segProcData.traverse2.BezSegInx = BezSegInx;
		segProcData.traverse2.segs_backwards = segs_backwards;
		LOG( log_traverseCornu, 2, ( "  TravCornu-Tr1 SI%d D%0.3f B%d RS%d \n",
		                             BezSegInx, dist, backwards, reverse_seg ) )
		SegProc( SEGPROC_TRAVERSE2, segPtr, &segProcData );		//Angle at pos2
		if ( segProcData.traverse2.dist <=
		     0 ) {				//-ve or zero distance left over so stop there
			*distR = 0;
			trvTrk->pos = segProcData.traverse2.pos;			//Use finishing pos
			trvTrk->angle = segProcData.traverse2.angle;		//Use finishing angle
			LOG( log_traverseCornu, 1, ( "TravCornu-Ex1 -> [%0.3f %0.3f] A%0.3f D%0.3f\n",
			                             trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR ) )
			return TRUE;
		}
		dist = segProcData.traverse2.dist;						//How far left?
		coOrd pos = segProcData.traverse2.pos;					//Will always be at a Bezseg end
		ANGLE_T angle = segProcData.traverse2.angle;			//Angle of end therefore
		segProcData.traverse1.angle = angle; 					//Set up Traverse1
		segProcData.traverse1.pos = pos;
		inx = cornu_backwards?inx-1:inx
		      +1;						//Here's where the global segment direction comes in
		LOG( log_traverseCornu, 2, ( "  TravCornu-Loop D%0.3f A%0.3f I%d \n", dist,
		                             angle, inx ) )
	}
	//Ran out of Bez-Segs so punt to next Track
	*distR = dist;												//Tell caller what dist is left

	trvTrk->pos = GetTrkEndPos(trk,ep);							//Which end were we heading for?
	trvTrk->angle = NormalizeAngle(GetTrkEndAngle(trk,
	                               ep));    //+(cornu_backwards?180:0));
	trvTrk->trk = GetTrkEndTrk(trk,ep);							//go onto next track (or NULL)

	if (trvTrk->trk==NULL) {
		trvTrk->pos = pos1;
		return TRUE;
	}
	LOG( log_traverseCornu, 1, ( "TravCornu-Ex2 --> [%0.3f %0.3f] A%0.3f D%0.3f\n",
	                             trvTrk->pos.x, trvTrk->pos.y, trvTrk->angle, *distR ) )
	return TRUE;

}


static BOOL_T EnumerateCornu( track_p trk )
{

	if (trk != NULL) {
		struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
		DIST_T d;
		d = max(CornuOffsetLength(xx->arcSegs,-GetTrkGauge(trk)/2.0),
		        CornuOffsetLength(xx->arcSegs,GetTrkGauge(trk)/2.0));
		ScaleLengthIncrement( GetTrkScale(trk), d );
		return TRUE;
	}
	return FALSE;
}

static BOOL_T MergeCornu(
        track_p trk0,
        EPINX_T ep0,
        track_p trk1,
        EPINX_T ep1 )
{
	return FALSE;
}

BOOL_T GetBezierSegmentsFromCornu(track_p trk, dynArr_t * segs, BOOL_T track)
{
	struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	for (int i=0; i<xx->arcSegs.cnt; i++) {
		trkSeg_p p = &DYNARR_N(trkSeg_t,xx->arcSegs,i);
		if (p->type == SEG_BEZTRK) {
			if (track) {
				DYNARR_APPEND(trkSeg_t, * segs, 10);
				trkSeg_p segPtr = &DYNARR_N(trkSeg_t,* segs,segs->cnt-1);
				segPtr->type = SEG_BEZTRK;
				segPtr->color = wDrawColorBlack;
				segPtr->lineWidth = 0;
				DYNARR_FREE( trkSeg_t, segPtr->bezSegs );
				for (int j=0; j<4; j++) { segPtr->u.b.pos[j] = p->u.b.pos[j]; }
				FixUpBezierSeg(segPtr->u.b.pos,segPtr,TRUE);
			} else {
				for (int j=0; j<p->bezSegs.cnt; j++) {
					trkSeg_p bez_p = &DYNARR_N(trkSeg_t,p->bezSegs,j);
					DYNARR_APPEND(trkSeg_t, * segs, 10);
					trkSeg_p segPtr = &DYNARR_LAST(trkSeg_t,* segs);
					if (bez_p->type == SEG_CRVTRK) { segPtr->type = SEG_CRVLIN; }
					if (bez_p->type == SEG_STRTRK) { segPtr->type = SEG_STRLIN; }
					segPtr->u = bez_p->u;
					segPtr->lineWidth = bez_p->lineWidth;
					segPtr->color = bez_p->color;
				}
			}
		} else if (p->type == SEG_STRTRK) {
			DYNARR_APPEND(trkSeg_t, * segs, 1);
			trkSeg_p segPtr = &DYNARR_N(trkSeg_t,* segs,segs->cnt-1);
			segPtr->type = track?SEG_STRTRK:SEG_STRLIN;
			segPtr->color = wDrawColorBlack;
			segPtr->lineWidth = 0;
			for (int j=0; j<2; j++) { segPtr->u.l.pos[i] = p->u.l.pos[i]; }
			segPtr->u.l.angle = p->u.l.angle;
			segPtr->u.l.option = 0;
		} else if (p->type == SEG_CRVTRK) {
			DYNARR_APPEND(trkSeg_t, * segs, 1);
			trkSeg_p segPtr = &DYNARR_N(trkSeg_t,* segs,segs->cnt-1);
			segPtr->type = track?SEG_CRVTRK:SEG_CRVLIN;
			segPtr->color = wDrawColorBlack;
			segPtr->lineWidth = 0;
			segPtr->u.c.a0 = p->u.c.a0;
			segPtr->u.c.a1 = p->u.c.a1;
			segPtr->u.c.center = p->u.c.center;
			segPtr->u.c.radius = p->u.c.radius;
		}
	}
	return TRUE;
}


static DIST_T GetLengthCornu( track_p trk )
{
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	DIST_T length = 0.0;
	segProcData_t segProcData;
	for(int i=0; i<xx->arcSegs.cnt; i++) {
		trkSeg_t seg = DYNARR_N(trkSeg_t,xx->arcSegs,i);
		if (seg.type == SEG_FILCRCL) { continue; }
		SegProc(SEGPROC_LENGTH, &seg, &segProcData);
		length += segProcData.length.length;
	}
	return length;
}

EXPORT BOOL_T GetCornuMiddle( track_p trk, coOrd * pos)
{

	if (GetTrkType(trk) != T_CORNU) {
		return FALSE;
	}
	DIST_T length = GetLengthCornu(trk)/2;

	traverseTrack_t tp;
	tp.pos = GetTrkEndPos(trk,0);
	tp.angle = NormalizeAngle(GetTrkEndAngle(trk,0)+180.0);
	tp.trk = trk;
	tp.length = length;

	TraverseCornu(&tp,&length);

	*pos = tp.pos;

	return TRUE;

}


static BOOL_T GetParamsCornu( int inx, track_p trk, coOrd pos,
                              trackParams_t * params )
{
	int segInx, segInx2;
	BOOL_T back, negative;
	DIST_T d;
	struct extraDataCornu_t *xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	params->type = curveTypeCornu;
	params->track_angle =
	        GetAngleSegs(		  						//Find correct Segment and nearest point in it
	                xx->arcSegs.cnt,&DYNARR_N(trkSeg_t,xx->arcSegs,0),
	                &pos, &segInx, &d, &back, &segInx2, &negative );
	if (segInx ==-1) { return FALSE; }
	trkSeg_p segPtr = &DYNARR_N(trkSeg_t,xx->arcSegs,segInx);
	if (negative != back) { params->track_angle = NormalizeAngle(params->track_angle+180); }  //Cornu is in reverse
	if (segPtr->type == SEG_STRTRK) {
		params->arcR = 0.0;
	} else  if (segPtr->type == SEG_CRVTRK) {
		params->arcR = fabs(segPtr->u.c.radius);
		params->arcP = segPtr->u.c.center;
		params->arcA0 = segPtr->u.c.a0;
		params->arcA1 = segPtr->u.c.a1;
	} else if (segPtr->type == SEG_BEZTRK) {
		trkSeg_p segPtr2 = &DYNARR_N(trkSeg_t,segPtr->bezSegs,segInx2);
		if (segPtr2->type == SEG_STRTRK) {
			params->arcR = 0.0;
		} else if (segPtr2->type == SEG_CRVTRK) {
			params->arcR = fabs(segPtr2->u.c.radius);
			params->arcP = segPtr2->u.c.center;
			params->arcA0 = segPtr2->u.c.a0;
			params->arcA1 = segPtr2->u.c.a1;
		}
	}
	for (int i=0; i<2; i++) {
		params->cornuEnd[i] = xx->pos[i];
		params->cornuAngle[i] = xx->a[i];
		params->cornuRadius[i] = xx->r[i];
		params->cornuCenter[i] = xx->c[i];
	}
	params->len = xx->length;
	if ( inx == PARAMS_NODES ) {
		return FALSE;
	} else if ((inx == PARAMS_CORNU) || (inx == PARAMS_1ST_JOIN)
	           || (inx == PARAMS_2ND_JOIN) ) {
		params->ep = PickEndPoint( pos, trk);
	} else {
		params->ep = PickUnconnectedEndPointSilent( pos, trk );

	}
	if (params->ep == -1) { return FALSE; }

	if (params->ep>=0) {
		params->angle = GetTrkEndAngle(trk,params->ep);
	}

	return TRUE;

}



static BOOL_T QueryCornu( track_p trk, int query )
{
	switch ( query ) {
	case Q_CAN_GROUP:
		return FALSE;
		break;
	case Q_FLIP_ENDPTS:
	case Q_HAS_DESC:
		return TRUE;
		break;
	case Q_EXCEPTION: {
		struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
		return fabs(xx->minCurveRadius) < (GetLayoutMinTrackRadius()-EPSILON);
	}
	case Q_IS_CORNU:
		return TRUE;
		break;
	case Q_ISTRACK:
		return TRUE;
		break;
	case Q_CAN_PARALLEL:
		return TRUE;
		break;
	// case Q_MODIFY_CANT_SPLIT: Remove Split Restriction
	// case Q_CANNOT_BE_ON_END: Remove Restriction - Can have Cornu with no ends
	case Q_CANNOT_PLACE_TURNOUT:
		return FALSE;
		break;
	case Q_CORNU_CAN_MODIFY:
	case Q_IGNORE_EASEMENT_ON_EXTEND:
		return TRUE;
		break;
	case Q_MODIFY_CAN_SPLIT:
		return TRUE;
	default:
		return FALSE;
	}
}


static void FlipCornu(
        track_p trk,
        coOrd orig,
        ANGLE_T angle )
{
	struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	FlipPoint( &xx->pos[0], orig, angle );
	FlipPoint( &xx->pos[1], orig, angle );
	FlipPoint( &xx->c[0], orig, angle);
	FlipPoint( &xx->c[1], orig, angle);
	xx->a[0] = NormalizeAngle( 2*angle - xx->a[0] );
	xx->a[1] = NormalizeAngle( 2*angle - xx->a[1] );

	/* Reverse internals so that they match the new ends */
	coOrd pos_save = xx->pos[0];
	xx->pos[0] = xx->pos[1];
	xx->pos[1] = pos_save;
	ANGLE_T angle_save = xx->a[0];
	xx->a[0] = xx->a[1];
	xx->a[1] = angle_save;
	coOrd c_save = xx->c[0];
	xx->c[0] = xx->c[1];
	xx->c[1] = c_save;
	DIST_T rad_save = xx->r[0];
	xx->r[0] = xx->r[1];
	xx->r[1] = rad_save;

	RebuildCornu(trk);

}

static ANGLE_T GetAngleCornu(
        track_p trk,
        coOrd pos,
        EPINX_T * ep0,
        EPINX_T * ep1 )
{
	struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	ANGLE_T angle;
	BOOL_T back, neg;
	int indx;
	angle = GetAngleSegs( xx->arcSegs.cnt, &DYNARR_N(trkSeg_t,xx->arcSegs,0), &pos,
	                      &indx, NULL, &back, NULL, &neg );
	if (!back) { angle = NormalizeAngle(angle+180); }
	if ( ep0 ) { *ep0 = neg?1:0; }
	if ( ep1 ) { *ep1 = neg?0:1; }
	return angle;
}

BOOL_T GetCornuSegmentFromTrack(track_p trk, trkSeg_p seg_p)
{
	//TODO If we support Group
	return TRUE;
}

//static dynArr_t cornuSegs_da;

static BOOL_T MakeParallelCornu(
        track_p trk,
        coOrd pos,
        DIST_T sep,
        DIST_T factor,
        track_p * newTrkR,
        coOrd * p0R,
        coOrd * p1R,
        BOOL_T track )
{
	coOrd np[4], p, nc[2];
	ANGLE_T atrk, diff_a, na[2];
	DIST_T nr[2];


	//Produce cornu that is translated parallel to the existing Cornu
	// - not a precise result if the cornu end angles are not in the same general direction.
	// The expectation is that the user will have to adjust it - unless and until we produce
	// a new algo to adjust the control points to be parallel to the endpoints.

	p = pos;
	DistanceCornu(trk, &p);  //Find nearest point on curve
	struct extraDataCornu_t * xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	atrk = GetAngleSegs(xx->arcSegs.cnt,&DYNARR_N(trkSeg_t,xx->arcSegs,0),&p,
	                    NULL, NULL, NULL,NULL, NULL);
	diff_a = NormalizeAngle(FindAngle(pos,p)-atrk);  //we know it will be +/-90...
	//find parallel move x and y for points
	BOOL_T above = FALSE;
	if ( diff_a < 180 ) { above = TRUE; } //Above track
	if (xx->a[0] <180) { above = !above; }
	DIST_T sep0 = sep+((xx->r[0]!=0.0)?fabs(factor/xx->r[0]):0);
	DIST_T sep1 = sep+((xx->r[1]!=0.0)?fabs(factor/xx->r[1]):0);
	Translate(&np[0],xx->pos[0],xx->a[0]+(above?90:-90),sep0);
	Translate(&np[1],xx->pos[1],xx->a[1]+(above?-90:90),sep1);
	na[0]=xx->a[0];
	na[1]=xx->a[1];
	if (xx->r[0] != 0.0) {
		//Find angle between center and end angle of track
		ANGLE_T ea0 =
		        NormalizeAngle(FindAngle(xx->c[0],xx->pos[0])-xx->a[0]);
		if (ea0>180) { sep0 = -sep0; }
		nr[0]=xx->r[0]+(above?sep0:-sep0);           //Needs adjustment
		nc[0]=xx->c[0];
	} else {
		nr[0] = 0.0;
		nc[0] = zero;
	}

	if (xx->r[1] != 0.0) {
		ANGLE_T ea1 =
		        NormalizeAngle(FindAngle(xx->c[1],xx->pos[1])-xx->a[1]);
		if (ea1<180) { sep1 = -sep1; }
		nr[1]=xx->r[1]+(above?sep1:-sep1);            //Needs adjustment
		nc[1]=xx->c[1];
	} else {
		nr[1] = 0.0;
		nc[1] = zero;
	}

	if ( newTrkR ) {
		if (track) {
			*newTrkR = NewCornuTrack( np, nc, na, nr, NULL, 0);
			if (*newTrkR==NULL) {
				wBeep();
				InfoMessage(
				        _("Cornu Create Failed for p1[%0.3f,%0.3f] p2[%0.3f,%0.3f], c1[%0.3f,%0.3f] c2[%0.3f,%0.3f], a1=%0.3f a2=%0.3f, r1=%s r2=%s"),
				        np[0].x,np[0].y,
				        np[1].x,np[1].y,
				        nc[0].x,nc[0].y,
				        nc[1].x,nc[1].y,
				        na[0],na[1],
				        FormatDistance(nr[0]),FormatDistance(nr[1]));
				return FALSE;
			}
		} else {
			DYNARR_RESET( trkSeg_t, tempSegs_da );
			CallCornu0(np,nc,na,nr,&tempSegs_da,FALSE);
			*newTrkR = MakePolyLineFromSegs( zero, 0.0, &tempSegs_da );
		}

	} else {
		DYNARR_RESET( trkSeg_t, tempSegs_da );
		CallCornu0(np,nc,na,nr,&tempSegs_da,FALSE);
		if (!track) {
			for (int i=0; i<tempSegs_da.cnt; i++) {
				trkSeg_p seg = &tempSegs(i);
				if (seg->type == SEG_STRTRK) {
					seg->type = SEG_STRLIN;
					seg->color = wDrawColorBlack;
					seg->lineWidth = 0;
				}
				if (seg->type == SEG_CRVTRK) {
					seg->type = SEG_CRVLIN;
					seg->color = wDrawColorBlack;
					seg->lineWidth = 0;
				}
				if (seg->type == SEG_BEZTRK) {
					for (int j=0; j<seg->bezSegs.cnt; j++) {
						trkSeg_p bseg = &DYNARR_N(trkSeg_t,seg->bezSegs,j);
						if (bseg->type == SEG_STRTRK) {
							bseg->type = SEG_STRLIN;
							bseg->color = wDrawColorBlack;
							bseg->lineWidth = 0;
						}
						if (bseg->type == SEG_CRVTRK) {
							bseg->type = SEG_CRVLIN;
							bseg->color = wDrawColorBlack;
							bseg->lineWidth = 0;
						}
					}
					seg->type = SEG_BEZLIN;
					seg->color = wDrawColorBlack;
					seg->lineWidth = 0;
				}
			}
		}
	}
	if ( p0R ) { *p0R = np[0]; }
	if ( p1R ) { *p1R = np[1]; }
	return TRUE;
}

static BOOL_T TrimCornu( track_p trk, EPINX_T ep, DIST_T dist, coOrd endpos,
                         ANGLE_T angle, DIST_T radius, coOrd center )
{
	UndoModify(trk);
	if (dist>0.0 && dist<minLength) {
		UndrawNewTrack( trk );
		DeleteTrack(trk, TRUE);
		return FALSE;
	} else {
		struct extraDataCornu_t *xx;
		UndrawNewTrack( trk );
		xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
		xx->a[ep] = angle;
		xx->c[ep] = center;
		xx->r[ep] = radius;
		xx->pos[ep] = endpos;
		RebuildCornu(trk);
		SetTrkEndPoint(trk, ep, xx->pos[ep], xx->a[ep]);
		DrawNewTrack( trk );
	}
	return TRUE;
}

/*
 * When an undo is run, the array of segs is missing - they are not saved to the Undo log. So Undo calls this routine to
 * ensure
 * - that the Segs are restored and
 * - other fields reset.
 */
EXPORT BOOL_T RebuildCornu (track_p trk)
{
	struct extraDataCornu_t *xx;
	xx = GET_EXTRA_DATA(trk, T_CORNU, extraDataCornu_t);
	//NOTE - Original code did not Free .ptr? See CS:c1b85944f448
	DYNARR_INIT( trkSeg_t, xx->arcSegs );
	if (!FixUpCornu0(xx->pos,xx->c,xx->a,xx->r, xx)) { return FALSE; }
	ComputeCornuBoundingBox(trk, xx);
	return TRUE;
}


static wBool_t CompareCornu( track_cp trk1, track_cp trk2 )
{
	struct extraDataCornu_t *xx1 = GET_EXTRA_DATA( trk1, T_CORNU,
	                               extraDataCornu_t );
	struct extraDataCornu_t *xx2 = GET_EXTRA_DATA( trk2, T_CORNU,
	                               extraDataCornu_t );
	char * cp = message + strlen(message);
	REGRESS_CHECK_POS( "Pos[0]", xx1, xx2, pos[0] )
	REGRESS_CHECK_POS( "Pos[1]", xx1, xx2, pos[1] )
	REGRESS_CHECK_POS( "C[0]", xx1, xx2, c[0] )
	REGRESS_CHECK_POS( "C[1]", xx1, xx2, c[1] )
	REGRESS_CHECK_ANGLE( "A[0]", xx1, xx2, a[0] )
	REGRESS_CHECK_ANGLE( "A[1]", xx1, xx2, a[1] )
	REGRESS_CHECK_DIST( "R[0]", xx1, xx2, r[0] )
	REGRESS_CHECK_DIST( "R[1]", xx1, xx2, r[1] )
	REGRESS_CHECK_DIST( "MinCurveRadius", xx1, xx2, minCurveRadius )
	REGRESS_CHECK_DIST( "MaxRateofChange", xx1, xx2, maxRateofChange )
	REGRESS_CHECK_DIST( "Length", xx1, xx2, length )
	REGRESS_CHECK_ANGLE( "WindingAngle", xx1, xx2, windingAngle )
	// CHECK arcSegs
	REGRESS_CHECK_POS( "DescOff", xx1, xx2, descriptionOff )
	// CHECK cornuPath
	return TRUE;
}

static trackCmd_t cornuCmds = {
	"CORNU",
	DrawCornu,
	DistanceCornu,
	DescribeCornu,
	DeleteCornu,
	WriteCornu,
	ReadCornu,
	MoveCornu,
	RotateCornu,
	RescaleCornu,
	NULL,
	GetAngleCornu,
	SplitCornu,
	TraverseCornu,
	EnumerateCornu,
	NULL,	/* redraw */
	TrimCornu,   /* trim   */
	MergeCornu,
	NULL,   /* modify */
	GetLengthCornu,
	GetParamsCornu,
	MoveCornuEndPt, /* Move EndPt */
	QueryCornu,
	NULL,	/* ungroup */
	FlipCornu,
	NULL,
	NULL,
	NULL,
	MakeParallelCornu,
	NULL,
	RebuildCornu,
	NULL,
	NULL,
	NULL,
	CompareCornu
};





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




track_p NewCornuTrack(coOrd pos[2], coOrd center[2],ANGLE_T angle[2],
                      DIST_T radius[2], trkSeg_t * tempsegs, int count)
{
	struct extraDataCornu_t *xx;
	track_p p;
	p = NewTrack( 0, T_CORNU, 2, sizeof *xx );
	xx = GET_EXTRA_DATA(p, T_CORNU, extraDataCornu_t);
	xx->pos[0] = pos[0];
	xx->pos[1] = pos[1];
	xx->a[0] = angle[0];
	xx->a[1] = angle[1];
	xx->r[0] = radius[0];
	xx->r[1] = radius[1];
	xx->c[0] = center[0];
	xx->c[1] = center[1];

	if (!FixUpCornu0(xx->pos,xx->c,xx->a,xx->r, xx)) {
		ErrorMessage("Create Cornu Failed");
		return NULL;
	}
	LOG( log_cornu, 1,
	     ( "NewCornuTrack( EP1 %0.3f, %0.3f, EP2 %0.3f, %0.3f )  = %d\n", pos[0].x,
	       pos[0].y, pos[1].x, pos[1].y, GetTrkIndex(p) ) )
	ComputeCornuBoundingBox( p, xx );
	SetTrkEndPoint( p, 0, pos[0], xx->a[0]);
	SetTrkEndPoint( p, 1, pos[1], xx->a[1]);
	CheckTrackLength( p );
	SetTrkBits( p, TB_HIDEDESC );
	return p;
}


EXPORT void InitTrkCornu( void )
{
	T_CORNU = InitObject( &cornuCmds );
	log_cornu = LogFindIndex( "Cornu" );
	log_traverseCornu = LogFindIndex( "traverseCornu" );
}