/** /file celev.c
 *  ELEVATION
 */

/*  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include <math.h>
#include <string.h>

#include "cselect.h"
#include "cundo.h"
#include "custom.h"
#include "fileio.h"
#include "i18n.h"
#include "param.h"
#include "track.h"

static wWin_p elevW;

static long elevModeV;
static char elevStationV[STR_SIZE];
static DIST_T elevHeightV = 0.0;

static DIST_T elevOldValue;
static track_p elevTrk;
static EPINX_T elevEp;
static BOOL_T elevUndo = FALSE;

static char * elevModeLabels[] = { N_("None"), N_("Defined"), N_("Hidden"),
	N_("Computed"), N_("Grade"), N_("Station"), N_("Ignore"), NULL };
static paramFloatRange_t r_1000_1000 = { -1000, 1000 };

static paramData_t elevationPLs[] = {
#define I_MODE			(0)
	{ PD_RADIO, &elevModeV, "mode", 0, elevModeLabels, "" },
#define I_HEIGHT			(1)
	{ PD_FLOAT, &elevHeightV, "value", PDO_DIM|PDO_DLGNEWCOLUMN, &r_1000_1000 },
#define I_COMPUTED			(2)
	{ PD_MESSAGE, NULL, "computed", 0, (void*)80 },
#define I_GRADE			(3)
	{ PD_MESSAGE, NULL, "grade", 0, (void*)80 },
#define I_STATION			(4)
	{ PD_STRING, elevStationV, "station", PDO_DLGUNDERCMDBUTT|PDO_STRINGLIMITLENGTH, (void*)200, NULL, 0, 0, sizeof(elevStationV)} };
static paramGroup_t elevationPG = { "elev", 0, elevationPLs, sizeof elevationPLs/sizeof elevationPLs[0] };


static void LayoutElevW(
		paramData_t * pd,
		int inx,
		wPos_t colX,
		wPos_t * x,
		wPos_t * y )
{
	static wPos_t h = 0;
	switch ( inx ) {
	case I_HEIGHT:
		h = wControlGetHeight( elevationPLs[I_MODE].control )/((sizeof elevModeLabels/sizeof elevModeLabels[0])-1);
#ifndef WINDOWS
		h += 3;
#endif
		*y = DlgSepTop+h+h/2;
		break;
	case I_COMPUTED:
	case I_GRADE:
	case I_STATION:
		*y = DlgSepTop+h*(inx+1);
		break;
	}
}


static int GetElevMode( void )
{
	int mode;
	int newMode;
	static int modeMap[7] = { ELEV_NONE, ELEV_DEF|ELEV_VISIBLE, ELEV_DEF, ELEV_COMP|ELEV_VISIBLE, ELEV_GRADE|ELEV_VISIBLE, ELEV_STATION|ELEV_VISIBLE, ELEV_IGNORE };
	mode = (int)elevModeV;
	if (mode<0||mode>=7)
		return -1;
	newMode = modeMap[mode];
	return newMode;
}


#ifdef LATER
static void DoElevRadio( long mode, void * context )
{
	if ( mode < 0 || mode >= 7 )
		return;
#ifdef ELEVM
	ParamLoadMessage( elevMessageM, "" );
#endif
	ParamControlActive( &elevationPG, I_HEIGHT, FALSE );
	ParamControlActive( &elevationPG, I_STATION, FALSE );
	switch ( mode ) {
	case 0:
		break;
	case 1:
	case 2:
		ParamControlActive( &elevationPG, I_HEIGHT, TRUE );
		break;
	case 3:
	case 4:
#ifdef OLDELEV
		if ( !( (rc0 == FDE_DEF && rc1 == FDE_DEF) ||
				(rc0 == FDE_DEF && rc1 == FDE_END) ||
				(rc0 == FDE_END && rc1 == FDE_DEF) ) ) {
			ParamLoadMessage( &elevationPG, I_MSG, _("There are no reachable Defined Elevations") );
			ParamLoadControl( &elevationPG, I_MODE );
			return;
		}
#endif
		break;
	case 5:
		wControlActive( (wControl_p)elevStationS, TRUE );
		break;
	}
	elevModeV = mode;
	DoElevUpdate( NULL, 1, NULL );
}
#endif


static void DoElevUpdate( paramGroup_p pg, int inx, void * valueP )
{
	int oldMode, newMode;
	coOrd pos;
	DIST_T elevNewValue, elevOldValue, diff;
	DIST_T radius;

	if ( inx == 0 ) {
		long mode = *(long*)valueP;
		if ( mode < 0 || mode >= 7 )
			return;
#ifdef ELEVM
		ParamLoadMessage( elevMessageM, "" );
#endif
		ParamControlActive( &elevationPG, I_HEIGHT, FALSE );
		ParamControlActive( &elevationPG, I_STATION, FALSE );
		switch ( mode ) {
		case 0:
			break;
		case 1:
		case 2:
			ParamControlActive( &elevationPG, I_HEIGHT, TRUE );
			break;
		case 3:
		case 4:
#ifdef OLDELEV
			if ( !( (rc0 == FDE_DEF && rc1 == FDE_DEF) ||
					(rc0 == FDE_DEF && rc1 == FDE_END) ||
					(rc0 == FDE_END && rc1 == FDE_DEF) ) ) {
				ParamLoadMessage( &elevationPG, I_MSG, _("There are no reachable Defined Elevations") );
				ParamLoadControl( &elevationPG, I_MODE );
				return;
			}
#endif
			break;
		case 5:
			ParamControlActive( &elevationPG, I_STATION, TRUE );
			break;
		}
		elevModeV = mode;
	}
	ParamLoadData( &elevationPG );
	newMode = GetElevMode();
	if (newMode == -1)
		return;
	if (elevTrk == NULL)
		return;
	oldMode = GetTrkEndElevUnmaskedMode( elevTrk, elevEp );
	elevNewValue = 0.0;
	if ((newMode&ELEV_MASK) == ELEV_DEF)
		elevNewValue = elevHeightV;
	if (oldMode == newMode) {
		if ((newMode&ELEV_MASK) == ELEV_DEF) {
			elevOldValue = GetTrkEndElevHeight( elevTrk, elevEp );
			diff = fabs( elevOldValue-elevNewValue );
			if ( diff < 0.02 )
				return;
		} else if ((newMode&ELEV_MASK) == ELEV_STATION) {
			if ( strcmp(elevStationV, GetTrkEndElevStation( elevTrk, elevEp ) ) == 0)
				return;
		} else {
			return;
		}
	}
	if (elevUndo == FALSE) {
		UndoStart( _("Set Elevation"), "Set Elevation" );
		elevUndo = TRUE;
	}
	pos = GetTrkEndPos( elevTrk, elevEp );
	radius = 0.05*mainD.scale;
	if ( radius < trackGauge/2.0 )
		radius = trackGauge/2.0;
	if ( (oldMode&ELEV_MASK)==ELEV_DEF || (oldMode&ELEV_MASK)==ELEV_IGNORE )
		DrawFillCircle( &tempD, pos, radius,
			((oldMode&ELEV_MASK)==ELEV_DEF?elevColorDefined:elevColorIgnore));
	HilightSelectedEndPt(FALSE, elevTrk, elevEp);
	UpdateTrkEndElev( elevTrk, elevEp, newMode, elevNewValue, elevStationV );
	HilightSelectedEndPt(TRUE, elevTrk, elevEp);
	if ( (newMode&ELEV_MASK)==ELEV_DEF || (newMode&ELEV_MASK)==ELEV_IGNORE )
		DrawFillCircle( &tempD, pos, radius,
			((newMode&ELEV_MASK)==ELEV_DEF?elevColorDefined:elevColorIgnore));
}


static void DoElevDone( void * arg )
{
	DoElevUpdate( NULL, 1, NULL );
	HilightElevations( FALSE );
	HilightSelectedEndPt( FALSE, elevTrk, elevEp );
	elevTrk = NULL;
	Reset();
}


static void DoElevHilight( void * junk )
{
	HilightElevations( TRUE );
}


static void ElevSelect( track_p trk, EPINX_T ep )
{
	int mode;
	DIST_T elevX, grade, elev, dist;
	long radio;
	BOOL_T computedOk;
	BOOL_T gradeOk = TRUE;
	track_p trk1;
	EPINX_T ep1;

	DoElevUpdate( NULL, 1, NULL );
	elevOldValue = 0.0;
	elevHeightV = 0.0;
	elevStationV[0] = 0;
	HilightSelectedEndPt(FALSE, elevTrk, elevEp);
	elevTrk = trk;
	elevEp = ep;
	mode = GetTrkEndElevUnmaskedMode( trk, ep );
	ParamLoadControls( &elevationPG );
	ParamControlActive( &elevationPG, I_MODE, TRUE );
	ParamControlActive( &elevationPG, I_HEIGHT, FALSE );
	ParamControlActive( &elevationPG, I_STATION, FALSE );
	ParamLoadMessage( &elevationPG, I_COMPUTED, "" );
	ParamLoadMessage( &elevationPG, I_GRADE, "" );
	switch (mode & ELEV_MASK) {
	case ELEV_NONE:
		radio = 0;
		break;
	case ELEV_DEF:
		if ( mode & ELEV_VISIBLE )
			radio = 1;
		else
			radio = 2;
		elevHeightV = GetTrkEndElevHeight(trk,ep);
		elevOldValue = elevHeightV;
		ParamLoadControl( &elevationPG, I_HEIGHT );
		ParamControlActive( &elevationPG, I_HEIGHT, TRUE );
		break;
	case ELEV_COMP:
		radio = 3;
		break;
	case ELEV_GRADE:
		radio = 4;
		break;
	case ELEV_STATION:
		radio = 5;
		strcpy( elevStationV, GetTrkEndElevStation(trk,ep) );
		ParamLoadControl( &elevationPG, I_STATION );
		ParamControlActive( &elevationPG, I_STATION, TRUE );
		break;
	case ELEV_IGNORE:
		radio = 6;
		break;
	default:
		radio = 0;
	}
	elevModeV = radio;
	ParamLoadControl( &elevationPG, I_MODE );
#ifdef OLDELEV
if (oldElevationEvaluation) {
	int dir;
	ANGLE_T a;
	int rc0, rc1;
	DIST_T elev0, elev1, dist0, dist1;
	a = GetTrkEndAngle( trk, ep );
	dir = ( a > 270 || a < 90 );
	rc0 = FindDefinedElev( trk, ep, dir, FALSE, &elev0, &dist0 );
	rc1 = FindDefinedElev( trk, ep, 1-dir, FALSE, &elev1, &dist1 );
	if ( rc0 == FDE_DEF ) {
		sprintf( message, _("Elev = %s"), FormatDistance(elev0) );
		ParamLoadMessage( elev1ElevM, message );
		sprintf( message, _("Dist = %s"), FormatDistance(dist0) );
		ParamLoadMessage( elev1DistM, message );
#ifdef LATER
		if (dist0 > 0.1)
			sprintf( message, "%0.1f%%", elev0/dist0 );
		else
			sprintf( message, _("Undefined") );
		ParamLoadMessage( elev1GradeM, message );
#endif
	} else {
		ParamLoadMessage( elev1ElevM, "" );
		ParamLoadMessage( elev1DistM, "" );
		/*ParamLoadMessage( elev1GradeM, "" );*/
	}
	if ( rc1 == FDE_DEF ) {
		sprintf( message, _("Elev = %s"), FormatDistance(elev1) );
		ParamLoadMessage( elev2ElevM, message );
		sprintf( message, _("Dist = %s"), FormatDistance(dist1) );
		ParamLoadMessage( elev2DistM, message );
#ifdef LATER
		if (dist1 > 0.1)
			sprintf( message, "%0.1f%%", elev1/dist1 );
		else
			sprintf( message, _("Undefined") );
		ParamLoadMessage( elev2GradeM, message );
#endif
	} else {
		ParamLoadMessage( elev2ElevM, "" );
		ParamLoadMessage( elev2DistM, "" );
		/*ParamLoadMessage( elev2GradeM, "" );*/
	}
	computedOk = TRUE;
	if (rc0 == FDE_DEF && rc1 == FDE_DEF) {
		grade = (elev1-elev0)/(dist0+dist1);
		elevX = elev0 + grade*dist0;
	} else if (rc0 == FDE_DEF && rc1 == FDE_END) {
		grade = 0.0;
		elevX = elev0;
	} else if (rc0 == FDE_END && rc1 == FDE_DEF) {
		elevX = elev1;
		grade = 0.0;
	} else {
		gradeOk = FALSE;
		computedOk = FALSE;
	}
} else {
#endif
	gradeOk = ComputeElev( trk, ep, FALSE, &elevX, &grade );
	computedOk = TRUE;
#ifdef OLDELEV
}
#endif
	if (oldElevationEvaluation || computedOk) {
		sprintf( message, "%0.2f%s", PutDim( elevX ), (units==UNITS_METRIC?"cm":"\"") );
		ParamLoadMessage( &elevationPG, I_COMPUTED, message );
		if (gradeOk) {
			sprintf( message, "%0.1f%%", fabs(grade*100) );
		} else {
			if ( EndPtIsDefinedElev(trk,ep) ) {
				elev = GetElevation(trk);
				dist = GetTrkLength(trk,ep,-1);
				if (dist>0.1)
					sprintf( message, "%0.1f%%", fabs((elev-elevX)/dist)*100.0 );
				else
					sprintf( message, _("Undefined") );
				if ( (trk1=GetTrkEndTrk(trk,ep)) && (ep1=GetEndPtConnectedToMe(trk1,trk))>=0 ) {
					elev = GetElevation(trk1);
					dist = GetTrkLength(trk1,ep1,-1);
					if (dist>0.1)
						sprintf( message+strlen(message), " - %0.1f%%", fabs((elev-elevX)/dist)*100.0 );
					else
						sprintf( message+strlen(message), " - %s", _("Undefined") );
				}
			} else {
				strcpy( message, _("Undefined") );
			}
		}
		ParamLoadMessage( &elevationPG, I_GRADE, message );
		if ( (mode&ELEV_MASK)!=ELEV_DEF ) {
			elevHeightV = elevX;
			ParamLoadControl( &elevationPG, I_HEIGHT );
		}
	}
	HilightSelectedEndPt(TRUE, elevTrk, elevEp);
}


static STATUS_T CmdElevation( wAction_t action, coOrd pos )
{
	track_p trk0, trk1;
	EPINX_T ep0;
	int oldTrackCount;

	switch (action) {
	case C_START:
		if ( elevW == NULL )
			elevW = ParamCreateDialog( &elevationPG, MakeWindowTitle(_("Elevation")), _("Done"), DoElevDone, NULL, TRUE, LayoutElevW, 0, DoElevUpdate );
		elevModeV = 0;
		elevHeightV = 0.0;
		elevStationV[0] = 0;
		ParamLoadControls( &elevationPG );
		ParamGroupRecord( &elevationPG );
		wShow( elevW );
		ParamControlActive( &elevationPG, I_MODE, FALSE );
		ParamControlActive( &elevationPG, I_HEIGHT, FALSE );
		ParamControlActive( &elevationPG, I_STATION, FALSE );
		ParamLoadMessage( &elevationPG, I_COMPUTED, "" );
		ParamLoadMessage( &elevationPG, I_GRADE, "" );
		InfoMessage( _("Select End-Point") );
		HilightElevations( TRUE );
		elevTrk = NULL;
		elevUndo = FALSE;
		return C_CONTINUE;
	case C_RDOWN:
	case C_RMOVE:
	case C_RUP:
		CmdMoveDescription( action-C_RDOWN+C_DOWN, pos );
		return C_CONTINUE;
	case C_LCLICK:
		if ((trk0 = OnTrack( &pos, TRUE, TRUE )) == NULL) {
			return C_CONTINUE;
		}
		if ( (MyGetKeyState()&WKEY_SHIFT) ) {
			ep0 = PickEndPoint( pos, trk0 );
			UndoStart( _("Split Track"), "SplitTrack( T%d[%d] )", GetTrkIndex(trk0), ep0 );
			oldTrackCount = trackCount;
			if (!SplitTrack( trk0, pos, ep0, &trk1, FALSE ))
				return C_CONTINUE;
			ElevSelect( trk0, ep0 );
			UndoEnd();
			elevUndo = FALSE;
		} else {
			ep0 = PickEndPoint( pos, trk0 );
			ElevSelect( trk0, ep0 );
			return C_CONTINUE;
		}
		return C_CONTINUE;
	case C_OK:
		DoElevDone(NULL);
		return C_TERMINATE;
	case C_CANCEL:
		HilightElevations( FALSE );
		HilightSelectedEndPt( FALSE, elevTrk, elevEp );
		elevTrk = NULL;
		wHide( elevW );
		return C_TERMINATE;
	case C_REDRAW:
		DoElevHilight( NULL );
		HilightSelectedEndPt( TRUE, elevTrk, elevEp );
		return C_CONTINUE;
	}
	return C_CONTINUE;
}




#include "bitmaps/elev.xpm"

EXPORT void InitCmdElevation( wMenu_p menu )
{
	ParamRegister( &elevationPG );
	AddMenuButton( menu, CmdElevation, "cmdElevation", _("Elevation"), wIconCreatePixMap(elev_xpm), LEVEL0_50, IC_POPUP|IC_LCLICK, ACCL_ELEVATION, NULL );
}