/* $Header: https://oberon.oapd.inaf.it/shins/trunk/shins/src/iif/Interface/Ltcs/Iif/IifStar.cc 374 2018-01-11 11:43:51Z depa $
*/

#include <sstream>
#include <iomanip>
#include <cstdlib>
#include <cmath>
#include <Ltcs/Iif/IifStar.h>
#include <Nice/Map/SkyPoint.h>
#include <sofa.h>

#ifndef DR2D
/** 180 divided by pi.
* Same as in sofam.h
*/
#define DR2D 57.2957795130823208767981548141
#endif

#ifndef DAS2R
/** Arcseconds to radians
* Same as in sofam.h. Inverse of DR2D divided by 3600.
*/
#define DAS2R (4.848136811095359935899141e-6)
#endif

#ifndef DMAS2R
/** Milliarcseconds to radians.
* Same as in sofam.h
*/
#define DMAS2R (DAS2R / 1e3)
#endif


/*!****************
* @brief default ctor.
* Basically not useful, but to build an empty star catalogue
* @since 2017-01-18
*/
Ltcs::IifStar::IifStar()
	: position2()
{
	system = std::string("RADEC") ;
	equinox = std::string("J2000") ;
	/* default is 0.5 micron wavelength
	*/
	wavelength = 0.5 ;
} /* ctor */

/*!****************
* @brief Create from a given structure
* @param star The contents to be read in.
* @since 2017-03-17
*/
Ltcs::IifStar::IifStar(const lbto::position2 & star)
	: position2(star)
{
} /* ctor */

/*!****************
* @brief Ctor given just a position on the sky.
*  The proper motions, magnitudes etc are all kept at zero.
* @param ra Right ascension
* @param dec Declination
* @since 2017-01-21
*/
Ltcs::IifStar::IifStar(const Nice::DMS & ra, const Nice::DMS &dec)
{
	system = std::string("RADEC") ;
	equinox = std::string("J2000") ;
	/* default is 0.5 micron wavelenvth
	*/
	wavelength = 0.5 ;
	coord1 = ra.radians() ;
	coord2 = dec.radians() ;
} /* ctor */

/*!****************
* @brief Ctor with a specification of a single star.
*  This should be line conforming to 
*   http://wiki.lbto.org/bin/view/Commissioning/LBTOStarCatalogs
*   but not a comment line.
* @param str The string. This is typically a line of a star catalogue.
* @param typ The indication of how this string is formatted.
*    Should be "lbto" or "nomad". No other formats are implemented.
* @since 2017-01-20
*/
Ltcs::IifStar::IifStar(const std::string & str, const std::string & typ)
	: position2()
{
	system = std::string("RADEC") ;
	/* default is 0.5 micron wavelenvth
	*/
	wavelength = 0.5 ;
	if ( typ == "lbto")
	{
		colorType = std::string("B_R") ;

		int idx, rah, ram, decm ;
		double ras, decs ;
		std::string decdstr ;

		std::istringstream istr(str) ;
		istr >> idx >> rah >> ram >> ras >> decdstr >> decm >> decs 
			>> pmcoord1 >> pmcoord2 
			>> epoch >> equinox >> apparentMagnitude >> color ;

		/* convert proper motions from mas/yr to radians per year
		*/
		pmcoord1 *= DMAS2R ;
		pmcoord2 *= DMAS2R ;

		double ra = rah +ram/60.0 + ras/3600.0 ;
		coord1 = ra *15 /DR2D ;

		/* take care of not ignoring the sign in the declination 
		* if the deg component is zero...
		*/
		int decd  = atoi(decdstr.c_str()) ;
		double dec = fabs(decd)  +decm/60.0 + decs/3600.0 ;
		if ( decdstr.find('-') != std::string::npos)
			dec *= -1 ;

		coord2 = dec /DR2D ;
	}
	else if ( typ == "nomad")
	{
		equinox = std::string("J2000") ;
		colorType = std::string("B_R") ;
		epoch = 2000 ;

		/* 1 field an id
		* 2 RA in decimal degrees
		* 3 RA in decimal degrees
		* 4 sRa in mas
		* 5 sDec in mas
		* 6 sRaEp in mas
		* 7 sDeEp in mas
		* 8 mu (Ra) in mas/yr
		* 9 mu (Dec) in mas/yr
		* 10 smu (RA) in mas/yr
		* 11 smu (DEC) in mas/yr
		* 12 mag (B)
		* 13 mag (V)
		* 14 mag (R)
		* 15 mag (J)
		* 16 mag (H)
		* 17 mag (K)
		*/
		std::istringstream istr(str) ;

		std::string id ;
		double magB, magR , dummy;

		istr >> type >> coord1 >> coord2 >> dummy >> dummy >> dummy >> dummy
			>> pmcoord1 >> pmcoord2 >> dummy >> dummy>> magB >> dummy >> magR ; 
		apparentMagnitude = magR ;
		color = magB-magR ;

		/* convert RA and DEC from degrees to radians
		*/
		coord1 /= DR2D;
		coord2 /= DR2D;

		/* convert proper motions from mas/yr to radians per yer
		*/
		pmcoord1 *= DMAS2R ;
		pmcoord2 *= DMAS2R ;
	}
} /* ctor */

/*!**********************************
* @brief Construct a simple ra/dec pointing from this.
* @return Cast into the two angles of a ra/dec pair.
* @since 2017-01-18
*/
Ltcs::IifStar::operator Nice::SkyPoint() const
{
	// std::cerr << " coord1 " << coord1 << " coord2 " << coord2 << std::endl ;
	// std::cerr << " coord1 " << coord1*DR2D << " coord2 " << coord2*DR2D << std::endl ;
	return Nice::SkyPoint( Nice::DMS(coord1*DR2D), Nice::DMS(coord2*DR2D) ) ;
} /* operator SkyPoint */

/*!**********************************
* @brief Distance to another star in units of radians.
* @param oth The other star to which the distance (along a great arc) is measured.
* @return The distance on the sky in degrees.
* @since 2017-01-18
*/
double Ltcs::IifStar::distance(const Ltcs::IifStar & oth) const
{
	/* the angularDistance gives a Nice::DMS and the cast operator
	* turns this into degrees.
	*/
	return Nice::SkyPoint(*this).angularDistanceTo(Nice::SkyPoint(oth)) ;
} /* distance() */

/*!**********************************
* @brief A construed object name derived from the coordinates.
* @return The local radec[] numbers are shown as JHHMMSS+-DDMMSS
* @since 2017-03-30
*/
std::string Ltcs::IifStar::object() const
{
	char obj[15] ;
	obj[0]='J' ;
	/* convert to hours and degrees */
	double alpha = coord1*DR2D/15.0 ;
	int hr = floor(alpha) ;
	alpha -= hr ; 
	alpha *= 60.0 ;
	int mi = floor(alpha) ;
	alpha -= mi ; 
	alpha *= 60.0 ;
	int se = floor(alpha) ;
	sprintf(obj+1, "%02d%02d%02d", hr, mi, se);

	double delta = fabs(coord2*DR2D) ;
	hr = floor(delta) ;
	delta -= hr ; 
	delta *= 60.0 ;
	mi = floor(delta) ;
	delta -= mi ; 
	delta *= 60.0 ;
	se = floor(delta) ;
	sprintf(obj+7, "%s%02d%02d%02d", (coord2 >0) ? "+" : "-" , hr, mi, se);

	return std::string(obj) ;
} /* object() */

#pragma GCC diagnostic ignored "-Wconversion"
/*!*******************************
* @brief Convert the Ra and Dec coordinates to a LBTO format
* @return A string of the form HH MM SS.ssss   sDD HH MM SS.sss .
*/
std::string Ltcs::IifStar::toLbtoLoc() const
{
	std::ostringstream out ;

	/* we cannot use the DMS::toHMSString() etc because these
	* insert filler stuff like 'h' or 'm' or colons and because they
	* don't allow to set the precisions...
	*/

	/* ra converted from radians to units of hours (ie degrees divided by 15)
	*/
	double ra= coord1*DR2D/15.0 ;
	int h = ra ;

	out << std::fixed << std::setw(2) << std::setfill('0') << h ;
	ra -= h ;
	ra *= 60.0 ;
	int m = ra ; // minutes

	out << " " << std::setw(2) << std::setfill('0') << m ;
	ra -= m ;
	ra *= 60.0 ; // seconds

	out << " " << std::setw(7) << std::setprecision(4) << std::setfill('0') << ra ;
	out << "   " ;

	/* dec converted from radians to units of degrees
	*/
	double dec = coord2*DR2D ;
	if ( dec < 0.0)
		out << "-" ;
	else
		out << "+" ;
	dec=fabs(dec) ;

	int d = dec ;

	out << std::setw(2) << std::setfill('0') << d ;
	dec -= d ;
	dec *= 60.0 ;
	m = dec ; // arcminutes

	out << " " << std::setw(2) << std::setfill('0') << m ;
	dec -= m ;
	dec *= 60.0 ; // arcseconds
	out << " " << std::fixed << std::setw(6) << std::setprecision(3) << std::setfill('0') << dec ;

	return out.str() ;
} /* toLbtoLoc */

/*!*******************************
* @brief Convert the position2 structure to a Python list.
* @return A string of the form ['type',coord1,coord2,....,DECRate]
*/
std::string Ltcs::IifStar::toPosition2Py() const
{
	std::ostringstream out ;
	out << "lbto.position2("
		<< "'" << type << "'"
		<< "," << std::setprecision(8) << coord1
		<< "," << coord2
		<< ",'" << system << "'"
		<< "," << std::setprecision(4) << epoch
		<< ",'" << equinox << "'"
		<< "," << pmcoord1
		<< "," << pmcoord2
		<< "," << apparentMagnitude
		<< ",'" << filter << "'"
		<< "," << color
		<< ",'" << colorType << "'"
		<< "," << wavelength
		<< ",'" << file << "'"
		<< "," << time
		<< "," << RARate
		<< "," << DECRate << ")" ;

	return out.str() ;
} /* toPosition2Py */

/*!************************************************
* @brief Comparison of two stars by brightness.
*
* @return True (considered smaller) if  'first' has smaller brightness than the second.
*   We do not try to cross-compile magnitudes at different wavelengths and 
*   just consider the bare magnitudes independent of the wavelength or color index.
* @author Richard J. Mathar
* @since 2017-01-18
*/
bool Ltcs::operator< (const Ltcs::IifStar & first , const Ltcs::IifStar & secnd)
{
        if ( first.apparentMagnitude < secnd.apparentMagnitude )
                return true;
        else 
                return false ;
} /* operator< */

/*!*********************************
* @brief Apply a proper motion
*
* @param t The time when the motion ought to be finished.
* @return a star moved from its epoch to the epoch of t according to its proper motion vector.
* @since 2017-02-28
*/
Ltcs::IifStar Ltcs::IifStar::propMot(const Nice::Date & t) const
{
	/* direction of proper motion in tangent plane at celestial sphere.
	* tau=0 is along +alpha, tau=90 is along +delta. Expressed in radians
	*/
	const double nu = atan2(pmcoord2,pmcoord1) ;

	/* proper motion in radians per year
	*/
	double v = hypot(pmcoord1,pmcoord1) ;

	/* generate Gregorian year month and day of t
	*/
	const struct tm ts = t.toTimeStruct(true) ;

	/* portions of julian date of the SOFA library. Convert time to Julian date
	*/
	double djmNow[2] ;
	iauCal2jd(ts.tm_year+1900, ts.tm_mon+1, ts.tm_mday, &djmNow[0], &djmNow[1]) ;

	/* convert also the epoch of the catalogue to Julian date
	*/
	double djmCat[2] ;
	iauEpj2jd(epoch, &djmCat[0], &djmCat[1]) ;

	/* convert difference in Julian date into years. Multiply velocity to get radians of the angle
	*/
	v *= (djmNow[0]-djmCat[0]+djmNow[1]-djmCat[1])/DTY ;

	/* cartesian coordinates of the star in celestial sphere at J2000.
	* cos(delta)*cos(alpha), cos(delta)*sin(alpha), sin(delta)
	*/
	double s[3] ;
#if 0
	s[0] = cos(coord2)*cos(coord1) ;
	s[1] = cos(coord2)*sin(coord1) ;
	s[2] = sin(coord2) ;
#else
	iauS2c(coord1,coord2,s) ;
#endif

	/* cartesian coordinates of the rotation axis
	*/
	double ax[3] ;
	ax[0] = -sin(coord2)*cos(nu)*cos(coord1)+sin(coord1)*sin(nu) ;
	ax[1] = -sin(coord2)*cos(nu)*sin(coord1)-cos(coord1)*sin(nu) ;
	ax[2] = cos(coord2)*cos(nu) ;

	/* the matrix of the rotation
	*/
	double Rot[3][3] ;
#if 0
	/* Doubtful. Seems to generate a rotation matrix for the opposite direction, meaning: a passive rotation,
	* which would need the reversed angle ...
	*/
	ax[0] *= -v ;
	ax[1] *= -v ;
	ax[2] *= -v ;
	idauRv2m(ax,Rot) ;
#else
	for(int i=0 ; i < 3 ; i++)
	for(int j=0 ; j < 3 ; j++)
	{
		Rot[i][j] = (1-cos(v))*ax[i]*ax[j] ;
		if ( i == j)
			Rot[i][j] += cos(v) ;
		else
		{
			/* need epsilon(ijk), completely antisymm tensor, where k<>i, k <> j, already j<> i known
			*/
			int k ;
			if ( (i+1)% 3 != j)
				k = (i+1)%3 ;
			else
				k = (i+2)%3 ;
			int eps = ( (j+1) % 3 == k) ? 1 : -1 ;
			Rot[i][j] -= sin(v)*eps*ax[k] ;
		}
	}
#endif

	/* construct new Cartesian vector after motion
	*/
	double sprime[3] ;
#if 0
	for(int i=0 ; i < 3 ; i++)
		sprime[i] = Rot[j][0]*s[0] + Rot[j][1]*s[1] + Rot[j][2]*s[2] ;
#else
	iauRxp(Rot,s,sprime) ;
#endif

	/* construct new star at that coordinate
	*/
	Ltcs::IifStar moved(*this) ;
	moved.epoch = iauEpj(djmNow[0], djmNow[1]) ;

	iauC2s(sprime, & moved.coord1, & moved.coord2) ;

	/* wrap right ascension into the positive quadrant
	*/
	moved.coord1 = iauAnp(moved.coord1) ;

	/* todo : actually we should also convert the proper motion to where it is at the end....
	*/
	return moved ;
} /* propMot */

/*!*********************************
* @brief Apply a proper motion
*
* @return a star moved from its epoch to now according to its proper motion vector.
* @since 2017-02-28
*/
Ltcs::IifStar Ltcs::IifStar::propMot() const
{
	return propMot(Nice::Date::now()) ;
} /* propMot */

/*!*********************************
* @brief Apply proper motion and precession from catalogue origin to some other time.
*
* @param t The time when the coordinates should be valid.
* @return a star moved from its epoch to the coordinates at time t.
*
* @warning This should not be called in conjunction with propMot().
*   The two functions are to be used exclusively, not in succession.
*
* @since 2017-06-20
*/
Ltcs::IifStar Ltcs::IifStar::precess(const Nice::Date & t) const
{
	/* remove the cosine(delta) factor from the proper motion in alpha.
	* So pmcoord1 is dRa/dt*cos(delta), and pr is dRa/dt.
	* All units are radians and radians per year.
	* Parallax is unknown, so considered zero. Note that we use TT as MJD
	* and neglect the order of seconds that should be used to get TBD of MJD.
	*/
	const double pr  = pmcoord1/cos(coord2) ;
	const double mjd = t.toMJD() ;

	/* values returned by the IAU function
	*/
	double ri, di, eo ;
	iauAtci13(coord1, coord2, pr, pmcoord2, 0., 0., DJM0, mjd, & ri, & di, & eo) ;

	/* construct new star at that coordinate
	*/
	Ltcs::IifStar moved(*this) ;

	moved.epoch = iauEpj(DJM0, mjd) ;

	std::ostringstream jep ;
	jep << "J" << (int) (moved.epoch) ;
	moved.equinox = jep.str() ;

	/* wrap right ascension into the positive quadrant
	*/
	moved.coord1 = iauAnp(ri-eo) ;
	moved.coord2 = di ;

	/* todo : actually we should also convert the proper motion to where it is at the end....
	* But for the Linc-Nirvana case that is irrelevant because the proper motion will
	* not be used to convert the result again to another epoch.
	*/
	return moved ;
} /* precess */

/*!*********************************
* @brief Precess equatorial coordinate from the catalogues epoch to now.
*
* @return a star moved from its epoch to now according to proper motion and Earth precession.
* @since 2017-03-01
*/
Ltcs::IifStar Ltcs::IifStar::precess() const
{
	return precess(Nice::Date::now()) ;
} /* precess */

/*!***********************************
* @brief compute the apparent Greenwich sidereal time.
* @param[in] t The civil time in UTC time scales.
*   The default is "now", the time of the function call.
* @return Greenwich apparent (true) sidereal time.
*   The return value is basically an angle with the usual equivalence that 360 deg = 24 hrs.
*   For human read purposes this is to be rendered with
*   value.reduce().toHMSString() to get hours, minutes and seconds.
*/
Nice::DMS Ltcs::IifStar::getGST(const Nice::Date & t)
{
	/* convert calendar to julian day
	*/
	const struct tm tstru = t.toTimeStruct(true) ;
	const int iy = tstru.tm_year+1900 ;
	const int im = tstru.tm_mon+1 ;
	const int id = tstru.tm_mday ;

	/* get fractional part of the day; 24 hours equal 1 unit here
	*/
	const double fracday = (tstru.tm_hour+tstru.tm_min/60.0 + tstru.tm_sec/3600.0)/24.0 ;

	/* convert Unix day to Julian date (UTC). In the following the
	* split second difference between UTC and UT1 will be ignored.
	* For the purpose of this calculation, within 1 sec, UT1=UTC
	* and we don't know better, so we do not call iauUTCut1() here explicitly
	*/
	double djm0, djm ;
	iauCal2jd(iy,im,id,&djm0,&djm) ;

	/* add fractional days */
	djm += fracday ;

	/* get estimated difference TAI minus UTC; as of early 2017 close to +37 seconds
	*/
	double deltat ;
	iauDat(iy,im,id,fracday,&deltat) ;

	/* convert time to TAI; note that the interface wants to have UTC minus TAI as argument,
	* so we have to put the negative value into the argument (!).
	*/
	double tai0, tai ;
	iauUt1tai(djm0,djm,-deltat,&tai0,&tai) ;

	/* convert time to TT.
	*/
	double tt0, tt ;
	iauTaitt(tai0,tai,&tt0,&tt) ;

	/* the sofa routine returns st in units of radians.
	*/
	double st = iauGst06a(djm0,djm,tt0,tt) ;
	Nice::DMS lst ;
	lst.setRadians(st) ;

	return lst ;
} /* getGST */

/*!***********************************
* @brief compute the local sidereal time.
* @param t The civil time in UTC time scales.
*   The default is "now", the time of the function call.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return Apparent (a.k.a true) sidereal time.
*   Effectively the Greenwich sidereal time plus the (East) longitude of the observer.
*   The return value is basically an angle. For human read purposes
*   this is to be rendered with value.reduce().toHMSString() to get hours, minutes and seconds.
*   Subtract the right ascension to get hour angle.
*/
Nice::DMS Ltcs::IifStar::getLST(const Nice::Date & t, const Nice::GeoLocation & geosite)
{
	/* Greenwich sidereal time plus east longitude of observatory
	*/
	Nice::DMS gst = getGST(t) ;
	gst += geosite.Longitude ;

	/* get an angle wrapped into the 0..360 deg range
	*/
	const double wrapped = gst.reduce() ;
	gst.setD(wrapped) ;

	return gst ;
} /* getLST */

/*!***********************************
* @brief compute the parallactic angle.
* @param t The civil time in UTC time scales.
*   The default is "now", the time of the function call.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return Parallactic angle of this star at the given time.
* @since 2017-04-06
*/
Nice::DMS Ltcs::IifStar::getPang(const Nice::Date & t, const Nice::GeoLocation & geosite) const
{
	Nice::DMS lst = getLST(t,geosite) ;
	return ((Nice::SkyPoint)(*this)).pang(lst,geosite) ;
} /* getPang */

/*!***********************************
* @brief compute the horizontal coordinates (altitude, azimuth)
* @param t The civil time in UTC time scales.
*   The default is "now", the time of the function call.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return The two coordinates in the horizontal system.
* @since 2017-05-29
*/
Nice::SkyPoint Ltcs::IifStar::getHoriz(const Nice::Date & t, const Nice::GeoLocation & geosite) const
{
	Nice::DMS lst = getLST(t,geosite) ;
	Nice::SkyPoint targ = (Nice::SkyPoint)(*this) ;
	targ.EquatorialToHorizontal(lst,geosite.Latitude) ;
	return targ ;
} /* getPang */

/*!***********************************
* @brief compute the first time derivative of the parallactic angle.
* @param t The civil time in UTC time scales.
*   The default is "now", the time of the function call.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return derivative of parallactic angle of this star at the given time per second.
* @since 2017-04-06
*/
Nice::DMS Ltcs::IifStar::getPangDot(const Nice::Date & t, const Nice::GeoLocation & geosite)
{
	Nice::DMS lst = getLST(t,geosite) ;
	return ((Nice::SkyPoint)(*this)).pangDot(lst,geosite) ;
} /* getPangDot */

/** The time resolution (seconds) of estimating derotator times til limits
*/
#define LIIF_DEROT_PARSE_RESOL 20

/*!***********************************
* @brief compute a best start angle for derotation.
*  The parallactic angle is computed from the start time into the future with
*  20 seconds of resolution, monitoring the maximum and minimum angles.
*  The angle is computed which lets this derotation stay within the limits
*  for the longest possible duration.
* @param t The civil time in UTC time scales at which derotation should start.
*  Note that this can be a very rough estimate (of the order of 1 or 2 minutes) because
*  the actual start of the derotation will determine the derotator angles. This here
*  is just to get an estimate about the rough extrapolation of the trajectory into
*  the future.
* @param lowLim The lower limit switch of the motor (in degrees).
*   This is usually obtained by converting the TRAVELRANGE[0] by the TRANS.DEGREE of the configuration.
* @param hiLim The high limit switch (in degrees)
*   This is usually obtained by converting the TRAVELRANGE[1] by the TRANS.DEGREE of the configuration.
* @param dirScale The sign of this value must be positive if positive steps lead to positive angles, otherwise negative.
*   So the sign should be the sign of the factor in TRANS.DEGREE in the configuration file,
*   that means negative for the GWS DX as of 2017-05-03, otherwise positive.
*   The absolute value should be the absolute value of CFG.DIRECTION, which is
*   0.5 for the two K-mirrors and 1 for the 3 other cases.
* @param durat The maximum time of extrapolating the trajectory (in seconds)
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return The motor angle (in degrees) that maximises the time until the trajectory steps outside the limit switches.
*   This is in units of the nominal motor angles as used elsewhere, taking
*   sign and value of dirScale into account. The value could be used straight away
*   to send the derototar motor to that angle to start derotation at time t.
* @since 2017-05-03
*/
Nice::DMS Ltcs::IifStar::derotStartAng(Nice::Date t, const double lowLim, const double hiLim,
	const float dirScale, const int durat, const Nice::GeoLocation & geosite) const
{
	/* Amplitude of angle (optical!) supported by the motor/derotator,
	* undoing the factor 1/2 (resp. 2) implied by the K-mirrors.
	* todo: perhaps throw an exception if >360 or hiLim <= lowLim... or |dirScale |<0.4
	*/
	double motRange = fabs((hiLim-lowLim)/dirScale) ;

	// std::cerr <<__FILE__ << " motrange " << motRange << std::endl ;
	/* less than 0.1 degree of free range... is probably an error and
	* we return just the middle of the range.
	*/
	if (motRange < 0.1)
		return (hiLim+lowLim)/2.0 ;

	/* parallactic angle at t=0
	*/
	double p0 = getPang(t,geosite).degrees() ;

	/* parse the trajectory in 20 seconds steps; this time resolution sufficient for all realistic
	* durations of derotator trajectories 
	*/
	const Nice::Time tdiff=Nice::Time::seconds(LIIF_DEROT_PARSE_RESOL) ;

	/* minimum and maximum angle along the parallactic trajectory
	*/
	double prange[2] ={p0,p0};

	/* remember the previous angle to be aware of any -pi...+pi or +pi..-pi wrap around
	*/
	double pprev = p0 ;

	/* accumulated saw tooth offset (multiples of 2*pi) from wrap arounds
	*/
	double sawOffs =0.0 ;

	/* there is a definite upper estimate of running the motors for 10 hrs
	* (one night) on the same target...
	* 12 hrs divided by tdiff gives 2160 samples maximum on the time axis.
	* Use this estimate just to avoid an infinite loop here. This number of
	* hours can be large (much larger than the configured derotator polynomial time)
	* because we sample the trajectory only on rough time scales, and this is a cheap computation..
	*/
	for(int i=1; i < durat/LIIF_DEROT_PARSE_RESOL ; i++)
	{
		/* compute parallactic angle LIIF_DEROT_PARSE_RESOL seconds later.
		*/
		t += tdiff ;
		double p = getPang(t,geosite).degrees() ;

		/* detect and remove wrap around +pi -> -pi
		*/
		if ( p-pprev < -180)
		{
			sawOffs += 360 ;
			p += 360 ;
		}
		/* detect and remove wrap around -pi -> +pi
		*/
		else if ( p-pprev > 180)
		{
			sawOffs -= 360 ;
			p -= 360 ;
		}

		// std::cerr <<__FILE__ << " " << __LINE__ << " t=" << t << " p= " << p << std::endl ;
		/* we're not updating prange[0,1] right away because at exit of the loop
		* the variables should still be inside the range of the motor angle....
		* So pmin and pmax are temporary buffer variables before the break.
		*/
		const double pmin = std::min(prange[0],p) ;
		const double pmax = std::max(prange[1],p) ;
		if ( pmax-pmin > motRange)
			break ;
		prange[0] = pmin ;
		prange[1] = pmax ;

		pprev = p ;
	}

	// std::cerr <<__FILE__ << " " << __LINE__ << " p0=" << p0 
	// << " p= " << prange[0] << "..." << prange[1] << " lim=" << lowLim << ".." << hiLim <<std::endl ;

	/* There are cases, where prange[0..1] span a range which is less than
	* motrange (because either the trajectory is so "flat" or beause the time
	* spam is short). Then we try to place the middle of the prange into
	* the middle of the motor range, meaning, we try to start somewhere near
	* the middle of the motor range because starting there is (on the average) cheaper
	* than starting at one of the end switches. The artimetics means that we add a value x to
	* prange[1] and subtract a value to prange[0] until the (new) values have the motrange span.
	* prange[1]+x-(prange[0]-x) = motrange. 2*x=motrange-prange[1]+prange[0].
	*/
	const double pspill  = (motRange-prange[1]+prange[0])/2. ;
	if (pspill > 0.)
	{
		prange[1] += pspill ;
		prange[0] -= pspill ;
	}

	double mang ;
	/* for all 5 motors of LN, the motors should run
	* with the same sign as the parallactic angle
	* (because the derotator polynomials run with the negative sign of the steps
	* and the steps to be
	* applied are the negative values of the polynomials...)
	*/
	if ( dirScale > 0. )
	{
		/*
		* If dirScale>0, lowLim<hiLim , map prange[0] to lowLim
		* and prange[1] to hiLim and take the motor angle
		* by linear interpolation of p0 between prange[0] and prange[1].
		* (p0-prange[0])*|dirScale| = result-lowLim .
		*
		* If dirScale>0, hiLim<loLim , map prange[0] to hiLim
		* and prange[1] to lowLim and take the motor angle
		* by linear interpolation of p0 between prange[0] and prange[1].
		* (p0-prange[0])*|dirScale| = result-hiLim.
		*/
		mang = (p0-prange[0])*dirScale + std::min(lowLim,hiLim) ;
	}
	else
	{
		/* 
		* If dirScale<0, lowLim<hiLim , map prange[0] to lowLim
		* and prange[1] to hiLim and take the motor angle
		* by linear interpolation of p0 between prange[0] and prange[1].
		* (prange[1]-p0)*|dirScale| = (p0-prang[1])*dirScale = result-lowLim.
		*
		* If dirScale<0, hiLim<LowLim , map prange[0] to hiLim
		* and prange[1] to lowLim and take the motor angle
		* by linear interpolation of p0 between prange[0] and prange[1].
		* (prange[1]-p0)*|dirScale| = (p0-prange[1])*dirScale = result-hiLim.
		*/
		mang = (p0-prange[1])*dirScale + std::min(lowLim,hiLim) ;
	}
	return Nice::DMS(mang) ;
} /* derotStartAng */

/*!***********************************
* @brief compute a proposal for a best start angle for derotation.
*  The parallactic angle is computed from the start time into the future with
*  20 seconds of resolution, monitoring the maximum and minimum angles.
*  The angle is computed which lets this derotation stay within the limits
*  for the longest possible duration.
* @param t The civil time in UTC time scales at which derotation should start.
* @param travelrange The motor limit switches in units of steps.
*   This is usually obtained by reading the TRAVELRANGE in the configuration.
* @param transDegree The conversion matrix for conversion of steps to degress.
*   This is usually obtained by reading TRANS.DEGREE of the configuration.
* @param cfgDirection The value of CFG.DIRECTION of the configuration.
*   The absolute value is 0.5 for K-mirrors and 1.0 for the others.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return The motor angle (in degrees) that maximises the time until the trajectory steps outside the limit switches.
*   This is in units of the nominal motor angles as used elsewhere.
*   This could be used straight away
*   to send the derototar motor to that angle to start derotation at time t.
* @since 2017-05-03
*/
Nice::DMS Ltcs::IifStar::derotStartAng(Nice::Date t, const Nice::Point & travelrange,
	const Nice::Point & transDegree, const float cfgDirection,
	const int durat, const Nice::GeoLocation & geosite) const
{
	/* forward to the other signature... Convert the configuration parameters
	* to the interface that deals with angles as the basic piece of information
	* Narrow the travelrange by some portion of a degree to avoid being stuck in one of the limits:
	* We know that travelrange.x() < travelrange.y(), but lowLim and hiLim do not have
	* such a rigid ordering, because transDegree.x() may be negative. The simplest way
	* to narrow the range is to add a small number to travelrange.x() and subtract a
	* small number from travelrange.y(), "small" in units of steps.
	*/
	const double lowLim = (travelrange.x()+100)*transDegree.x()+transDegree.y() ;
	const double hiLim = (travelrange.y()-100)*transDegree.x()+transDegree.y() ;
	const float dirScale  = (transDegree.x() > 0 ) ? fabs(cfgDirection) : -fabs(cfgDirection) ;
	return derotStartAng(t,lowLim,hiLim,dirScale,durat,geosite) ;
} /* derotStartAng */

#if 0
/*!***********************************
* @brief Compute the time left for derotation until reaching the limit switch.
*  The parallactic angle is computed from the start time into the future with
*  20 seconds of resolution, monitoring the maximum and minimum angles.
*  Apparently this function is not called from anywhere.
* @param t The civil time in UTC time scales at which derotation should start.
* @param lowLim The lower limit switch of the motor (in degrees).
*   This is usually obtained by converting the TRAVELRANGE[0] by the TRANS.DEGREE of the configuration.
* @param hiLim The high limit switch (in degrees)
*   This is usually obtained by converting the TRAVELRANGE[1] by the TRANS.DEGREE of the configuration.
* @param mang0 The nominal motor angle at time t (in degrees)
* @param dirScale The sign of this value must be positive if positive steps lead to positive angles, otherwise negative.
*   So the sign should be the sign of the factor in TRANS.DEGREE in the configuration file,
*   that means negative for the GWS DX as of 2017-05-03, otherwise positive.
*   The absolute value should be the absolute value of CFG.DIRECTION, which is
*   0.5 for the two K-mirrors and 1 for the 3 other cases.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return The time left (after t) until the derotation reaches one of the limit switches.
*   This value is rounded down to 20 seconds of resolution
* @since 2017-05-08
*/
Nice::Time Ltcs::IifStar::derotResidTime(Nice::Date t, const double lowLim, const double hiLim,
	const double mang0, const float dirScale, const Nice::GeoLocation & geosite) const
{
	/* parallactic angle at t=0
	*/
	double p0 = getPang(t,geosite).degrees() ;

	// std::cerr <<__FILE__ << " p0 " << p0 << "deg\n" ;
	/* parse the trajectory in 20 seconds steps; this time resolution sufficient for all realistic
	* durations of derotator trajectories 
	*/
	const Nice::Time tdiff=Nice::Time::seconds(LIIF_DEROT_PARSE_RESOL) ;

	/* remember the previous angle to notice the -pi...+pi or +pi..-pi wrap around
	*/
	double pprev = p0 ;

	/* saw tooth offset (multiples of 2pi) from wrap arounds
	*/
	double sawOffs =0.0 ;

	int i=1;
	/* there is a definite upper estimate of running the motors for 10 hrs
	* (one night) onthe same target...
	* 12 hrs divided by tdiff gives 2160 samples maximum on the time axis.
	* Use this estimate just to avoid an infinite loop here.
	*/
	for( ; i < 12*3600/LIIF_DEROT_PARSE_RESOL ; i++)
	{
		/* compute parallactic angle for LIIF_DEROT_PARSE_RESOL seconds later.
		*/
		t += tdiff ;
		double p = getPang(t,geosite).degrees()+sawOffs ;

		/* detect and remove wrap around +pi -> -pi
		*/
		if ( p-pprev < -180)
		{
			sawOffs += 360 ;
			p += 360 ;
		}
		/* detect and remove wrap around -pi -> +pi
		*/
		else if ( p-pprev > 180)
		{
			sawOffs -= 360 ;
			p -= 360 ;
		}
		//std::cerr <<__FILE__ << " p " << p << " " << t << std::endl ;

		/* scale angle: if parall. angle changes by p-p0, then motor angle changes by mdiff.
		* dirScale*(p-p0) = mdiff.  mdiff = mang-mang0.
		*/
		double mang = (p-p0)*dirScale +mang0 ;
		//std::cerr <<__FILE__ << " mang " << mang << std::endl ;
		/* we're not updating prange[0,1] right away because at exit of the loop
		* the variables should still be inside the range of the motor angle....
		* So pmin and pmax are temporary buffer variables before the break.
		*/
		if ( mang < std::min(hiLim,lowLim) || mang > std::max(hiLim,lowLim) )
			break ;

		pprev = p ;
	}

	return Nice::Time::seconds((i-1)*LIIF_DEROT_PARSE_RESOL) ;

} /* derotResidTime */
#endif /* 0 */
#undef LIIF_DEROT_PARSE_RESOL

#if 0
/*!***********************************
* @brief Compute the time left for derotation until reaching the limit switch.
*  The parallactic angle is computed from the start time into the future with
*  20 seconds of resolution, monitoring the maximum and minimum angles.
*  Apparently this function is not called from anywhere.
* @param t The civil time in UTC time scales at which derotation should start.
* @param mang0 The nominal motor angle at time t (in degrees)
* @param travelrange The motor limit switches in units of steps.
*   This is usually obtained by reading the TRAVELRANGE in the configuration.
* @param transDegree The conversion matrix for conversion of steps to degress.
*   This is usually obtained by reading TRANS.DEGREE of the configuration.
* @param cfgDirection The value of CFG.DIRECTION of the configuration.
*   The absolute value is 0.5 for K-mirrors and 1.0 for the others.
* @param geosite Implies the geographic longitude of the observatory.
*   The default is the LBTO in Arizona, the default ctor for the GeoLocation.
* @return The time left (after t) until the derotation reaches one of the limit switches.
*   This value is rounded down to 20 seconds of resolution
* @since 2017-05-08
*/
Nice::Time Ltcs::IifStar::derotResidTime( Nice::Date t, const double mang0,
	const Nice::Point & travelrange,
	const Nice::Point & transDegree, const float cfgDirection,
	const Nice::GeoLocation & geosite) const
{
	/* forward to the other signature... Convert the configuration parameters
	* to the interface that deals with angles as the basic piece of information
	*/
	const double lowLim = travelrange.x()*transDegree.x()+transDegree.y() ;
	const double hiLim = travelrange.y()*transDegree.x()+transDegree.y() ;
	const float dirScale  = (transDegree.x() > 0 ) ? fabs(cfgDirection) : -fabs(cfgDirection) ;
	// std::cerr <<__FILE__ << " lowLim " << lowLim << " hiLim " << hiLim <<" dirscale " << dirScale << std::endl ;
	return derotResidTime(t,lowLim,hiLim,mang0,dirScale,geosite) ;
} /* derotResidTime */
#endif /* 0 */


/*!***********************************
* @brief Open a firefox window to show the current region of the sky in Aladin's DSS2 .
*  Opens a firefox screen with a 6 arcmin field-of-view around this target.
* @since 2017-06-26
*/
void Ltcs::IifStar::aladin() const
{
#if 0
	// example for 04:15:48 +31:43:08:
	// cmd << "firefox -geometry 850x835 http://aladin.u-strasbg.fr/AladinLite/?target=04%3A15%3A48%20%2B31%3A43%3A08&fov=0.18&survey=P%2F2MASS%2Fcolor" ;
#endif
	std::ostringstream cmd ;
	// cmd << "firefox -width 850 -height 835 -no-remote 'http://aladin.u-strasbg.fr/AladinLite/?target=" ;
	//cmd << "firefox -width 850 -height 835 -new-window 'http://aladin.u-strasbg.fr/AladinLite/?target=" ;
	cmd << "firefox -width 850 -height 835 'http://aladin.u-strasbg.fr/AladinLite/?target=" ;

	/* RA spec
	*/

	/* convert to hours and degrees. Space is 32=0x20. Colon is 58=0x3a. Slash is 47=0x2f */
	double alpha = coord1*DR2D/15.0 ;
	int hr = floor(alpha) ;
	cmd << std::fixed << std::setw(2) << std::setfill('0') << hr << "%3A" ;
	alpha -= hr ; 
	alpha *= 60.0 ;
	int mi = floor(alpha) ;
	cmd << std::fixed << std::setw(2) << std::setfill('0') << mi << "%3A" ;
	alpha -= mi ; 
	alpha *= 60.0 ;
	cmd << std::fixed << std::setw(4) << std::setprecision(1) << std::setfill('0') << alpha << "%20" ;

	/* DEC spec; start with plus or minus. Plus is 43=0x2b. Minus is hyphen is 45=0x2d
	*/
	if ( coord2 > 0)
		cmd << "%2B" ;
	else
		cmd << "%2D" ;

	double delta = fabs(coord2*DR2D) ;
	hr = floor(delta) ;
	cmd << std::fixed << std::setw(2) << std::setfill('0') << hr << "%3A" ;
	delta -= hr ; 
	delta *= 60.0 ;
	mi = floor(delta) ;
	cmd << std::fixed << std::setw(2) << std::setfill('0') << mi << "%3A" ;
	delta -= mi ; 
	delta *= 60.0 ;
	cmd << std::fixed << std::setw(2) << std::setprecision(1) << std::setfill('0') << alpha ;

	cmd << "&fov=0.1&survey=P%2FDSS2%2Fcolor'" ;

	/* command pushed in background, ignoring errors
	*/
	cmd << " >& /dev/null &" ;

	std::system(cmd.str().c_str()) ;
} /* aladin */

std::ostream &Ltcs::operator<< ( std::ostream & _os, const Ltcs::IifStar & _star )
{
    _os << (Nice::SkyPoint) _star;
    return _os;
}

std::istream &Ltcs::operator>> ( std::istream & _is, Ltcs::IifStar & _star )
{
//    using namespace Nice::Internal;
    Nice::SkyPoint sp;
    _is >> sp;
    _star = Ltcs::IifStar(sp.ra(), sp.dec());

    return _is;
}

std::ostream &lbto::operator<< ( std::ostream & _os, const lbto::position2 & _star )
{
    _os << (Ltcs::IifStar) _star;
    return _os;
}

std::istream &lbto::operator>> ( std::istream & _is, lbto::position2 & _star )
{
     Ltcs::IifStar sp;
    _is >> sp;
    _star = sp;

    return _is;
}

