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


#include <sstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <unistd.h>
#include <sys/stat.h>

#include <Ltcs/Iif/IifStarcat.h>

#ifndef DR2D
/** 180 divided by pi. Conversion factor radians to degrees
* see sofam.h
*/
#define DR2D (57.2957795130823208767981548141)
#endif

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

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

/*!****************
* @brief Define the reference star for the LBTO star list.
* @param cntr The star that will be placed at position 0 at the top of the list.
* @since 2017-01-18
*/
Ltcs::LbtStarCatSrt::LbtStarCatSrt(const IifStar & cntr)
{
    refstar = cntr ;
} /* ctor */

/*!****************
* @brief Compare two stars according to their distance to the reference direction.
* @param i First star of the pair.
* @param j Second star of the pair.
* @return True if the first star is closer to the reference star.
* @since 2017-01-18
*/
bool Ltcs::LbtStarCatSrt::operator() (const IifStar & i, const IifStar & j)
{
    return refstar.distance(i) < refstar.distance(j) ;
} /* operator() */


/*!****************
* @brief default ctor.
* This defines an empty star catalogue
* @since 2017-01-18
*/
Ltcs::IifStarcat::IifStarcat() : Ltcs::SeqIifStar()
{
} /* ctor */

/*!****************
* @brief Define the catalogue through a list of stars.
* @param slist The stars of the LBTO star catalogue that define the (original) set of stars.
* @since 2017-01-18
*/
Ltcs::IifStarcat::IifStarcat(const Ltcs::SeqIifStar & slist) : Ltcs::SeqIifStar(slist)
{
    /* copy constructor on all members
    */
} /* ctor */

/*!****************
* @brief Define the catalogue through a list of stars.
* @param slist The stars in the IIF style that define the (original) set of stars.
* @since 2017-03-19
*/
Ltcs::IifStarcat::IifStarcat(const lbto::SeqPos2 & slist)
    : Ltcs::SeqIifStar((Ltcs::SeqIifStar &)slist)
{
    /* copy constructor on all members
    */
} /* ctor */

/*!****************
* @brief Define the catalogue by reading an LBTO ASCII catalogue.
* @param ifnam The File stream of the ASCII file.
* @param typ Type of the ASCII file. Recognized is the 13-field "lbto" and the 17-field "nomad"
* @since 2017-01-18
*/
Ltcs::IifStarcat::IifStarcat(const std::string & ifnam, const std::string & typ)
    : Ltcs::SeqIifStar()
{
    std::ifstream ifi(ifnam.c_str()) ;
    if ( ! ifi)
    {
        std::cerr<< "Cannot open " << ifnam << std::endl ;
    }
    else
    {
        /* loop thru all input lines
        */
        while ( ! ifi.eof() )
        {
            if ( typ == "lbto" || typ == "nomad")
            {
                std::string lin;
                getline(ifi,lin) ;
                // std::cerr << __FILE__ << " " << lin << std::endl ;
                /* ignore empty lines and comments
                */
                if ( lin.substr(0,1) != "#" && lin.size() )
                {
                    IifStar s(lin,typ) ;
                    add(s) ;
                }
            }
        }
        ifi.close() ;
    }
} /* ctor */

/*!****************
* @brief Define the catalogue by merging one or more LBTO ASCII catalogues.
* @param ifnam The File names of the ASCII files.
* @param typ Type of the ASCII file. Recognized are "lbto" and "nomad."
* @since 2017-01-18
*/
Ltcs::IifStarcat::IifStarcat(const Nice::SeqString & ifnam, const std::string & typ)
   : Ltcs::SeqIifStar()
{
    /* loop over all input files and convert them one by one
    */
    for(size_t i = 0 ; i < ifnam.size() ; i++)
    {
        // std::cerr << __FILE__ << " " << ifnam[i] << std::endl ;
        IifStarcat subcat( ifnam[i],typ) ;
        add(subcat) ;
    }
} /* ctor */

/*!**********************************
* @brief Construct the lbto type of vector as used in SetStars2.
* @return The set of all stars rendered compatible with the IIF interface.
* @since 2017-04-06
*/
Ltcs::IifStarcat::operator lbto::SeqPos2() const
{
//     lbto::SeqPos2 slist ;
//     for(int i =0 ; i < (int) cat.size() ; i++)
//         slist.push_back(cat[i]) ;
// 
//     return slist ;
    return (lbto::SeqPos2 &) *this;

} /* operator SeqPos2 */


/*!****************
* @brief Add a star to the catalogue.
* @param s The star to be incorporated.
* @since 2017-01-18
*/
void Ltcs::IifStarcat::add(const Ltcs::IifStar & s)
{
    Ltcs::SeqIifStar::push_back(s);
} /* add() */

/*!****************
* @brief Add a star to the catalogue.
* @param p The star to be incorporated.
* @since 2017-01-18
*/
void Ltcs::IifStarcat::add(const lbto::position2 & p)
{
    IifStar s(p) ;
    Ltcs::SeqIifStar::push_back(p) ;
} /* add() */

/*!****************
* @brief Add all stars of another catalogue.
*   Warning: There is no criterion activated that eliminates duplicates.
* @param s The stars to be incorporated.
* @since 2017-01-20
*/
void Ltcs::IifStarcat::add(const Ltcs::IifStarcat & s)
{
    for(int i =0 ; i < (int) s.size() ; i++)
        add( s[i]) ;
} /* add() */

/*!****************
* @brief find brightest in the list
* @return the 0-based index of the star with the numerically smallest magnitude.
*   A value of -1 is returned if the list is empty.
* @since 2017-01-18
*/
int Ltcs::IifStarcat::brightest() const
{
    int idx=-1;
    double refmag = 999999  ;
    for(Ltcs::SeqIifStar::const_iterator it = Ltcs::SeqIifStar::begin(); it != Ltcs::SeqIifStar::end(); it++)
    {
        if ( it->apparentMagnitude < refmag)
        {
            idx++;
            refmag = it->apparentMagnitude ;
        }
    }
    return idx;
} /* brightest() */

/*!****************
* @brief remove the faintest stars from the catalogue
* @param[in] mag The fainting limit. The limiting magnitude.
*    This means stars with numerical magnitude larger than that will be removed.
* @since 2017-01-20
*/
void Ltcs::IifStarcat::rmFaint(const double mag)
{
      Ltcs::IifStarcat::erase( std::remove_if(Ltcs::SeqIifStar::begin(), 
                                Ltcs::SeqIifStar::end(), 
                                std::bind2nd(std::mem_fun_ref(&Ltcs::IifStar::apparentMagnitudeIsGreater), mag))
                         , Ltcs::SeqIifStar::end());
} /* rmFaint() */

/*!****************
* @brief remove a star at some index in the list.
* @param[in] idx The 0-based index.
*   If the index is outside the range of available stars, nothing happens.
* @since 2017-02-10
*/
void Ltcs::IifStarcat::rm(const size_t idx)
{
    if ( idx < Ltcs::IifStarcat::size()) Ltcs::IifStarcat::erase( Ltcs::IifStarcat::begin()+idx) ;
} /* rm() */

#if 0
/*!****************
* @brief retrieive a star by its index in the list.
* @param[in] idx The 0-based index.
*   If the index is outside the range of available stars, nothing happens.
* @since 2017-05-30
*/
lbto::position2 Ltcs::IifStarcat::at(const int idx) const
{
    if ( idx >=0 && idx < (int) cat.size())
        return cat[idx] ;
    else
        return lbto::position2() ;
} /* at() */
#endif

/*!****************
* @brief Remove stars outside a range of distances to the first in the list.
*
* Examples: keep all the stars in the region of the HWS up to 1 arcmin:
*    ... = keepByDist(-1.0, 0.000291)
* or to keep all the stars in the region of the GWS between 1 and 3 arcmin:
*    ... = keepByDist(0.000291, 0.000873)
* @param close The lower distance of the stars in the ring of stars to be kept (radian).
*   Should be set to a negative number if all stars up to some maximum distance are to be kept.
* @param far The upper distance of the stars in the ring of stars to be kept (radian).
*   Should be set to effectively 3.2 (above pi) to include all stars beyond some distanc.e
* @since 2017-03-01
*/
Ltcs::IifStarcat Ltcs::IifStarcat::keepByDist(const double & close, const double & far) const
{
    Ltcs::IifStarcat sublist ;
    if ( ! Ltcs::SeqIifStar::empty() )
    {
        const Ltcs::IifStar & targ = Ltcs::SeqIifStar::operator[](0) ;
        for(Ltcs::SeqIifStar::const_iterator it = Ltcs::SeqIifStar::begin(); it != Ltcs::SeqIifStar::end(); it++)
        {
            /* get distance in degrees and convert to radians
            */
            const double d = it->distance(targ)/DR2D ;
            if ( d >= close && d <= far)
                sublist.add( * it ) ;
        }
    }
    return sublist ;
} /* keepByDist() */

/*!****************
* @brief reduce the star set to a neighbourhood (region of interest) of some coordinate.
* @param coord1 RA of the center of interest (radian)
* @param coord2 DEC of the center of interest (radian)
* @param dist Maximum distance to the (ra,dec) (radian).
*   Effectively a radius working as a filter criterion on this set.
* @return A star sub-catalogue where all members farther than the distance from the center are removed.
* @since 2017-01-18
*/
Ltcs::IifStarcat Ltcs::IifStarcat::roi(const double coord1, const double coord2, const double dist) const
{
    Ltcs::SeqIifStar sublist ;
    /* the reference point towards which distances are measured
    * Note that this is declared const, as of 2017-01-18 the TAN does have that signature.
    */
    const Nice::SkyPoint cntr( Nice::DMS(coord1*DR2D), Nice::DMS(coord2*DR2D) ) ;

    /* convert distance to degrees to do this once instead of multiple times in the loop
    */
    const double distdegs = dist*DR2D ;
    for(Ltcs::SeqIifStar::const_iterator it = Ltcs::SeqIifStar::begin() ; it != Ltcs::SeqIifStar::end(); it++)
    {
        /* convert i-th star to a direction
        * Note this is declared const; as of 2017-01-18 the TAN does support that signature
        * in angularDistance().
        * const Nice::SkyPoint starNoi( Nice::DMS(cat[i].coord1*DR2D), Nice::DMS(cat[i].coord2*DR2D) ) ;
        */
        const Nice::SkyPoint starNoi = * it ;

        /* if i-th star has smaller distance than dist: add it
        */
        if ( starNoi.angularDistanceTo(cntr) < distdegs)
            sublist.push_back(* it) ;
    }
    return IifStarcat(sublist) ;
} /* roi() */

/*!****************
* @brief reduce the star set to a neighbourhood (region of interest) of some coordinate.
* @param cntr The star that defines the position of center of the search radius
* @param dist Maximum distance to the (ra,dec) (radian)
* @return A star catalogue where all members farther than the distance from the center are removed.
* @since 2017-01-18
*/
Ltcs::IifStarcat Ltcs::IifStarcat::roi(const IifStar & cntr, const double dist) const
{
    return  roi(cntr.coord1, cntr.coord2, dist) ;
} /* roi() */

/*!****************
* @brief Return a sequence of lines equivalent to the small catalogues of LBTO.
* @return A star catalogue where all members farther than the distance from the center are removed.
*   The brightest star is listed first, and the others follow one per line
*   as shown in http://wiki.lbto.org/bin/view/Commissioning/LBTOStarCatalogs
* @since 2017-01-18
*/
std::string Ltcs::IifStarcat::toLbtoFmt()
{
    if ( ! Ltcs::SeqIifStar::empty() )
    {
        const IifStar cntr = Ltcs::SeqIifStar::operator[](0);
        /* default cut-off distance is 4 arcmin = 4/60 deg = 4/60*Pi/180 rad
        */
        return toLbtoFmt(cntr, Nice::Date::now() , 4./60./DR2D) ;
    }

    return std::string("# no stars") ;
} /* toLbtoFmt() */

/*!****************
* @brief Return a sequence of lines equivalent to the small catalogues of LBTO.
* @param cntr The star that defines the position of center of the search radius
* @param t The time to which the coordinate transformations apply.
* @param dist The maximum distance for keeping stars, relative to cntr, in radians .
* @return A star catalogue where all members farther than the distance from the center are removed.
*   The brightest star is listed first, and the others follow one per line
*   as shown in http://wiki.lbto.org/bin/view/Commissioning/LBTOStarCatalogs
* @since 2017-01-18
*/
std::string Ltcs::IifStarcat::toLbtoFmt(const IifStar & cntr, const Nice::Date & t,
        const double dist)
{
    /* step 1: eliminate distant stars. Create a local star group.
    */
    Ltcs::IifStarcat locgrp = roi(cntr,dist) ;

#if 0
    /* the following steps are not done because we assume that the first
    * in the list is always the target, independent of brightness
    */
    /* step 2: find brightest
    */
    const int brigIdx = locgrp.brightest() ;
#else
    const int brigIdx = 0 ;
#endif
    std::ostringstream out ;

    if ( Ltcs::SeqIifStar::empty() )
    {
        out << "# no stars within radius of " ;
        out << dist*DR2D << " deg" ;
        return out.str() ;
    }

    /* step 3: sort by distance from brightest
    * std::sort(locgrp.begin(), locgrp.end() ) ; // no: that sorts by magnitude...
    */
    const Ltcs::LbtStarCatSrt bstar(Ltcs::SeqIifStar::operator[](brigIdx) ) ;
    std::sort(locgrp.begin(), locgrp.end(),bstar ) ;


    /* step 4: emit in the sorted order
    */
    time_t now = time(NULL) ;;
    out << "# generated by " << __FILE__ << " on " << asctime(gmtime(&now)) ;
    out << "# variable coordinates refer to " << t.toString(1,true) << " UTC " << std::endl ;

#if 0
	/* first a commented list in the standard position2 format as Python lists
	* This is more like noise and apparently not used by anyone or anywhere
	*/
	for(int i=0 ; i < (int)locgrp.cat.size() ; i++)
	{
		out << "# pos" << i << " = " << locgrp.cat[i].toPosition2Py() << std::endl ;
	}
#endif

    /* flag to indicate whether this is inside the HWS/Pcam 1 arcmin distance
    */
    bool hws = true ;

    /* flag to indicate whether this is inside the HWS/Pcam 3 arcmin distance
    * Note that the flag is also true if the star is closer than 1 arcmin to the target star.
    */
    bool gws = true ;

    Nice::GeoLocation geosite ;

    /* coordinate of the targe (at index 0) after proper motion and precession...
    */
    Ltcs::IifStar targMoved ;
    for(int i=0 ; i < (int)locgrp.size() ; i++)
    {
        /* distance to head element returned by distance() in degrees,
        * then converted to arcseconds. End-of-line
        */
        const double asdist = (i==0) ? 0.0 : 3600.*locgrp[i].distance(locgrp[0]) ;

        if ( i == 1)
            /* just as in the proposal in Ln-MPIA-TN-AIT-062
            */
            out << "#HWS" << std::endl ;

        if (asdist > 60.0 && hws)
        {
            // out << "# ----  1 arcmin cut of annular mirror and limit of HWS/PCam ------" << std::endl ;
            out << "#GWS" << std::endl ;
            hws = false ;
        }

        if (asdist > 180.0 && gws)
        {
            out << "#NONE ----- 3 arcmin limit of annular mirror here --" << std::endl ;
            gws = false ;
        }

        /* 1: index */
        out << std::noshowpos << std::setw(4) << i ;

        /* 2: hex-decimal RA and DEC without colons
        * const Nice::SkyPoint starNoi = cat[i] ;
        * const Nice::DMS r = starNoi.ra() ;
        * const Nice::DMS d = starNoi.dec() ;
        */
        out << "    " << locgrp[i].toLbtoLoc() ;

        /* 3: proper motions in RA and DEC
        * Is this with or without the cos(delta) factor???
        * convert from rad/yr to mas/yr
        */
        out << std::showpos ;
        out << std::setw(7) << std::showpos << std::setprecision(1) << std::fixed << std::right << locgrp[i].pmcoord1/DMAS2R ;
        out << std::setw(7) << std::showpos << std::setprecision(1) << std::fixed << std::right << locgrp[i].pmcoord2/DMAS2R ;

        /* 4: epoch and equinox
        */
        out << std::noshowpos ;
        out << "  " << locgrp[i].epoch ;
        out << "  " << locgrp[i].equinox ;

        /* 5: R2 magnitude: what's R2, something in the red?
        */
        out << " " << std::setw(7) << std::setprecision(3) << std::fixed << std::right << locgrp[i].apparentMagnitude ;

        /* 6: B-R color
        */
        out << std::setw(7) << std::setprecision(2) << std::fixed << std::right << locgrp[i].color ;

        out << std::setw(8) << std::setprecision(2) << std::fixed << std::right << asdist << std::endl ;

#if 0
        /* indicator of removed proper motions
        */
        const Ltcs::IifStar mvd = locgrp[i].propMot(t) ;
#else
        /* starting 2017-06-20 we also include precession...
        */
        const Ltcs::IifStar mvd = locgrp[i].precess(t) ;
#endif

        if ( i ==0 )
        {
            targMoved = mvd ;
            /* convert time to the parallactic angle of the center star (in radians)
            */
            Nice::DMS lst = IifStar::getLST(t) ;

            Nice::SkyPoint point(targMoved) ;

            out << "# Local Sidereal time " << lst.toHMSString() << std:: endl ;

            point.EquatorialToHorizontal(lst,geosite.Latitude ) ;
            out << "# Horizontal coordinates: altitude " << point.alt_ref().degrees() << " deg,"
                << " azimuth " << point.az_ref().degrees() << " deg" << std::endl ;

            Nice::DMS pang = targMoved.getPang(t) ;
            out << "# parallactic angle " << pang.degrees() << " deg" << std:: endl ;
        }

        out << "#" << std::noshowpos << std::setw(3) << i << "    " << mvd.toLbtoLoc() ;
        out << "                " << mvd.epoch << " " << mvd.equinox ;
        out << std::endl ;

    }

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

/*!***********************************
* @brief create a unique temporary empty file .
*  The functionality of mkstemp(3) but resolving the file name on the way.
* @param templ A prefix to faciliate lookup of the use.
*   Note that the XXXXXX marker of mkstemp(3) is not part of the parameter but added locally.
* @return the name of the new file.
* @since 2017-02-25
*/
std::string Ltcs::IifStarcat::mkstemp(const char * templ)
{
    /* room for the six X's and the \0
    */
    char *fname = new char[strlen(templ)+7] ;
    sprintf(fname,"%sXXXXXX",templ) ;
    const int fd = ::mkstemp(fname) ;
    delete [] fname ;

    /* create lookup name /proc/self/fd/FILEID
    */
    std::ostringstream procp ;
    procp << "/proc/self/fd/"  << fd ;
    std::string procpsearch = procp.str() ;

    /* read the contents fo the lookup name into tmpfn; this seems to contain blanks in most cases...
        char tmpfn[MAXPATHLEN] ;
        char tmpfn[MAX_PATH] ;
    */
#ifdef FILENAME_MAX
    char tmpfn[FILENAME_MAX] ;
#elif defined MAX_PATH
    char tmpfn[MAX_PATH] ;
#elif defined MAXNAMLEN
    char tmpfn[MAXNAMLEN] ;
#else
    char tmpfn[256] ;
#endif
    ssize_t nobytes = readlink(procpsearch.c_str(),tmpfn,sizeof(tmpfn)) ;
    if ( nobytes < 0 )
        return std::string() ;
    else
    {
        /* readlink does not terminate that with \0 !
        */
        tmpfn[nobytes] = '\0' ;
        return std::string(tmpfn) ;
    }
} /* mkstemp */

/*!*********************************
* @brief Apply the proper motions.
*
* @param t The time to which the motion ought to be promoted.
* @return The catalogue in which all stars are moved to the date t.
* @since 2017-02-28
*/
Ltcs::IifStarcat Ltcs::IifStarcat::propMot(const Nice::Date & t) const
{
    Ltcs::IifStarcat moved ;
//     for(int i =0 ; i < (int) cat.size() ; i++)
//         moved.add( cat[i].propMot(t) ) ;
    for(Ltcs::SeqIifStar::const_iterator it = Ltcs::SeqIifStar::begin(); it !=  Ltcs::SeqIifStar::end(); it++)
        moved.add( it->propMot(t) ) ;

    return moved ;
}

/*!*********************************
* @brief Apply all proper motions.
*
* @return a new catalogue with all stars moved from their epoch to now.
* @since 2017-02-28
*/
Ltcs::IifStarcat Ltcs::IifStarcat::propMot() const
{
    return propMot(Nice::Date::now()) ;
}

/*!*********************************
* @brief Correct for precession.
*
* @param t The time when the motion ought to be finished.
* @return The catalogue in which all stars are moved to the date t.
* @since 2017-03-01
*/
Ltcs::IifStarcat Ltcs::IifStarcat::precess(const Nice::Date & t) const
{
    Ltcs::IifStarcat moved ;
    for(Ltcs::SeqIifStar::const_iterator it = Ltcs::SeqIifStar::begin(); it !=  Ltcs::SeqIifStar::end(); it++)
        moved.add( it->precess(t) ) ;
    return moved ;
}

/*!*********************************
* @brief Correct equatorial coordinates for precession.
*
* @return a new catalogue with all stars moved from their epoch to the equator of dat.
* @since 2017-03-01
*/
Ltcs::IifStarcat Ltcs::IifStarcat::precess() const
{
    return precess(Nice::Date::now()) ;
}

/*!*********************************
* @brief Show the star system with Aladin of the university of Strasbourgh.
* This opens a brower window that shows the 3 1/2 arcmin surrounding of the target star.
*
* @since 2017-06-26
*/
void Ltcs::IifStarcat::aladin() const
{
	if ( ! Ltcs::IifStarcat::empty()  )
		Ltcs::IifStarcat::begin()->aladin() ;
} /* aladin */
