//# PointingDirectionCalculator.h: Does for MSes various fixes which do not involve calibrating.
//# Copyright (C) 2008
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This library is free software; you can redistribute it and/or modify it
//# under the terms of the GNU Library General Public License as published by
//# the Free Software Foundation; either version 2 of the License, or (at your
//# option) any later version.
//#
//# This library 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 Library General Public
//# License for more details.
//#
//# You should have received a copy of the GNU Library General Public License
//# along with this library; if not, write to the Free Software Foundation,
//# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#
//# $Id$
//#

#ifndef _SYNTHESIS_POINTING_DIRECTION_CALCULATOR_H_
#define _SYNTHESIS_POINTING_DIRECTION_CALCULATOR_H_

#include <casacore/casa/aipstype.h>
#include <casacore/casa/BasicSL/String.h>
#include <casacore/casa/Arrays/Matrix.h>
#include <casacore/casa/Arrays/Vector.h>
#include <casacore/casa/Quanta/MVDirection.h>
#include <casacore/casa/Utilities/CountedPtr.h>
#include <casacore/ms/MeasurementSets/MeasurementSet.h>
#include <casacore/ms/MeasurementSets/MSPointing.h>
#include <casacore/ms/MeasurementSets/MSPointingColumns.h>
#include <casacore/measures/Measures/MCDirection.h>
#include <casacore/measures/Measures/MDirection.h>
#include <casacore/measures/Measures/MPosition.h>
#include <casacore/measures/Measures/MEpoch.h>
#include <casacore/measures/Measures/MeasFrame.h>
#include <casacore/measures/TableMeasures/ScalarMeasColumn.h>
#include <casacore/measures/TableMeasures/ArrayMeasColumn.h>
#include <casacore/tables/Tables/ScalarColumn.h>
// CAS-8418 //
#include <synthesis/Utilities/SDPosInterpolator.h>
#include <memory>  // for unique_ptr<> 
#include <utility> // for std::pair

namespace casa {

///
// <summary>Calculates Pointing Direction Efficiently</summary>
//
//<use visibility=export>
//
// <reviewed reviewer="UNKNOWN" date="before2018/05/22" tests="" demos="">
// </reviewed>
//
// The PointingDirectionCalculator calculates pointing directions for selected MS rows.
// The calculation is based on the information recorded in MS POINTING table.
// In general, sampling intervals for spectral (autocorrelation) data (MS MAIN table)
// and pointing direction information (MS POINTING table) are independent. Thus,
// any interpolation in time domain should be necessary to calculate the pointing
// direction from the MS POINTING table. In addition, a direction reference frame
// in MS POINTING table may be different from the desired one. In that case,
// the frame conversion is also required. The class is intended to automate those
// complicated calculation. User can obtain a list of pointing directions by
// calling a few configuration method to select data and/or specify output direction
// reference frame. The class supports pointing direction calculation for moving
// objects. If the flag for moving source is turn on, returned direction will be
// shifted to the first timestamp.
//
// <prerequisite>
// </prerequisite>
//
// <etymology>
// </etymology>
//
// <synopsis>
// The PointingDirectionCalculator calculates pointing directions for selected
// MS rows. The calculation is based on the information recorded in MS POINTING
// table.
// </synopsis>
//
// <example>
// Here is a typical example for ALMA data. In ALMA, POINTING information is
// recorded in AZELGEO reference. However, there is a case that we need
// pointing direction in celestial coordinate such as J2000. In such case,
// the code will look like the following.
// <srcblock>
// MeasurementSet ms; // any MS
// PointingDirectionCalculator calculator(ms);
// calculator.setDirectionListMatrixShape(PointingDirectionCalculator::ROW_MAJOR);
// calculator.selectData(spw="19", antenna="PM01&&&"); // select spw 19 and antnena PM01
// calculator.setDirectionColumn("DIRECTION");
// calculator.setFrame("J2000");
// Matrix directions = calculator.getDirection();
// </srcblock>
// </example>
//
// <motivation>
// Pointing direction is one of the crucial information on single-dish data
// reduction. However, such information is not directly associated with
// spectral (autocorrelation) data. Instead, pointing directions must be
// calculated from the records in MS POINTING table. That calculation is
// sometimes complicated. It may contain an interpolation in time as well
// as a conversion to desired direction reference frame. This class is
// designed to automate such calculation. The implementation is somewhat
// specialized for the usecase of single-dish data reduction.
// </motivation>
//
//#! <templating arg=T>
//#! </templating>
//
// <thrown>
// </thrown>
//
// <todo asof="before2018/05/22">
//   <li> Define unit test
//   <li> Implement more sophisticated interpolation method such as cubic spline
// </todo>
///

class SplineInterpolation;       // CAS-8418 Forward Reference //

class PointingDirectionCalculator {
public:

    //+
    //  CAS-8418:  typedef of accessor_ and  Direction column types.
    //-
    typedef 
    casacore::MDirection (*ACCESSOR)(  casacore::MSPointingColumns &pointingColumns,
                                       casacore::uInt rownr);
    typedef
    enum DC_ { DIRECTION, TARGET, POINTING_OFFSET, SOURCE_OFFSET, ENCODER, nItems} PtColID;

    typedef casacore::Vector<casacore::Vector<casacore::Vector<casacore::Vector<casacore::Double> > > > 
    COEFF;

    // Enumerations for memory layout of the output pointing direction array.
    // User should select the layout according to an usercase of this class.
    enum MatrixShape {
        // Memory layout is "column major"
        COLUMN_MAJOR,
        // Memory layout is "row major"
        ROW_MAJOR
    };

    //+
    // Constructor
    //-
   
//+

//   Create Spline five Objects on PointingDirectionCalculator.
//
//   - The setDirectionColumn(name) performs all the initialization and make Coefficient table.
//   - In this constructor, init() and setDirectionColumn("DIRECTION") are called,
//     soon or later, setDirectionColumn will be called, whenever new Direction column is used
//     and interporation is made.
//   - The spline object is created by a column, which contains all antenna_Id.
//   - Cofficient table is obtained from SDPosInterpolation. 
//   - SplineInterpolation class in this file provides calculation but it is sort of customize 
//     for PoinitngDirectionCalculator.
//   - To see the initialization, please start from setDirectionColumn().  
//- 

    PointingDirectionCalculator(casacore::MeasurementSet const &ms);

    // Destructor
    ~PointingDirectionCalculator();

    // Select data in the given MS.
    // Each selection parameters accept MS data selection syntax.
    void selectData(casacore::String const &antenna = "", 
		casacore::String const &spw ="",
		casacore::String const &field = "", 
		casacore::String const &time = "",
            	casacore::String const &scan = "", 
		casacore::String const &feed = "",
            	casacore::String const &intent = "", 
		casacore::String const &observation = "", 
		casacore::String const &uvrange = "",
            	casacore::String const &msselect = "");

    //+
    // Select which POINTING column to use for pointing direction calculation.
    // Possible values are "DIRECTION" (default), "TARGET", "POINTING_OFFSET",
    // "SOURCE_OFFSET", and "ENCODER". These values are all case-sensitive.
    //
    // CAS-8418 Update
    //   Spline-Interpolation initialization is inserted for each POINTING Column.
    //   Once this is done, the object will be reused.
    //-
       
    void setDirectionColumn(casacore::String const &columnName = "DIRECTION");

    //+
    // Set output direction reference frame. This accepts reference strings which
    // <linkto class=MDirection>MDirection</linkto> can recognize.
    // If given string is invalid, the frame will be set to "J2000".
    //-
 
    void setFrame(casacore::String const frameType);

    //+
    // Set output direction matrix shape. If "ROW_MAJOR" is given, the shape will be
    // (2, nrow) where nrow is a number of selected rows in MS. If "COLUMN_MAJOR" is
    // set, the shape will be (nrow, 2). User can choose appropriate shape according
    // to the access pattern for the output direction matrix.
    //-

    void setDirectionListMatrixShape(
            PointingDirectionCalculator::MatrixShape const shape);
    
    //+
    // <group>
    // Set source name for the moving source.
    // The method accepts source names which <linkto class=MDirection>MDirection</linkto>
    // can recognize (e.g. "Moon", "Jupiter").
    // If given string is invalid, exception will be thrown. User can specify the moving source
    // using a string or the MDirection instance.
    
    void setMovingSource(casacore::String const sourceName);
    void setMovingSource(casacore::MDirection const &sourceDirection);
    
    // </group>

    //+
    // Clear the moving source setting.
    //-
    
    void unsetMovingSource();

    //+
    // Return number of rows for selected MS.
    //-

    casacore::uInt getNrowForSelectedMS() {return selectedMS_->nrow();}

    //+
    // Return direction type as a <linkto class=MDirection>MDirection::Types</linkto>
    // enum.
    //-

    casacore::MDirection::Types const &getDirectionType() {return directionType_;}

    //+
    // Return an information on the moving source as a <linkto class=MDirection>MDirection</linkto>
    // instance.
    //-
    
    casacore::MDirection const &getMovingSourceDirection() {return *movingSource_;}

    //+
    // Return pointing direction matrix. Its shape depends on the user set the shape to
    // "ROW_MAJOR" or "COLUMN_MAJOR". Returned directions are interpolated to timestamps
    // recorded in the selected MS, and are converted to desired direction reference
    // frame if necessary.
    //-     

    casacore::Matrix<casacore::Double> getDirection();

    //+
    // Return pointing direction for specified row.
    // If irow is larger than or equal to the number of rows for selected MS,
    // exception will be thrown.
    //-

    casacore::Vector<casacore::Double> getDirection(casacore::uInt irow);

    // <group>
    // Return a list of row ids for selected rows. The getRowId will return the ids
    // in selected MS. On the other hand, the getRowIdForOriginalMS will return
    // the ids in original MS.

    casacore::Vector<casacore::rownr_t> getRowId();
    casacore::Vector<casacore::rownr_t> getRowIdForOriginalMS();

    // </group>

    //+
    // Return a row id for specified row.
    // If irow is larger than or equal to the number of rows for selected MS,
    // exception will be thrown.
    //-

    casacore::rownr_t getRowId(casacore::uInt irow);

    //***************************************
    // CAS-8418  Spline Interpolation API 
    //***************************************
    
    // Spline interpolation Enable/Disable (true=ENABLE) 

    inline void setSplineInterpolation(bool mode) {useSplineInterpolation_ = mode;};

    // Curret Direction column (=accessor in this source) (for UT)

    inline PtColID  getCurretAccessorId()  { return  accessorId_ ; };

    // Exporting COEFF table. //

    bool    isCoefficientReady();   // true if current COEFF is availabe 
    COEFF   exportCoeff();          // returns copy of COEFF

private: 

    void init();
    void initPointingTable(casacore::Int const antennaId);
    void resetAntennaPosition(casacore::Int const antennaId);
    void resetTime(casacore::Double const timestamp);
    void inspectAntenna();
    void configureMovingSourceCorrection();

    casacore::Vector<casacore::Double> doGetDirection(casacore::uInt irow);
    casacore::Vector<casacore::Double> doGetDirection(casacore::uInt irow, casacore::uInt antID);

    // table access stuff

    casacore::CountedPtr<casacore::MeasurementSet> 		originalMS_;
    casacore::CountedPtr<casacore::MeasurementSet> 		selectedMS_;
    casacore::CountedPtr<casacore::MSPointing> 			pointingTable_;
    casacore::CountedPtr<casacore::MSPointingColumns>           pointingColumns_;
    casacore::ScalarMeasColumn<casacore::MEpoch> 		timeColumn_;
    casacore::ScalarColumn<casacore::Double> 			intervalColumn_;
    casacore::ScalarColumn<casacore::Int> 			antennaColumn_;
    casacore::String 						directionColumnName_;

    casacore::MDirection (*accessor_)(	casacore::MSPointingColumns &pointingColumns,
            				casacore::uInt rownr);
    // conversion stuff

    casacore::MPosition 			antennaPosition_;
    casacore::MEpoch 				referenceEpoch_;
    casacore::MeasFrame 			referenceFrame_;
    casacore::CountedPtr<casacore::MDirection::Convert> directionConvert_;
    casacore::MDirection::Types 			directionType_;
    casacore::CountedPtr<casacore::MDirection> 		movingSource_;
    casacore::CountedPtr<casacore::MDirection::Convert> movingSourceConvert_;

    void (*movingSourceCorrection_)(
            casacore::CountedPtr<casacore::MDirection::Convert> &convertToAzel,
            casacore::CountedPtr<casacore::MDirection::Convert> &convertToCelestial,
            casacore::Vector<casacore::Double> &direction);

    // other
   
    casacore::Vector<casacore::uInt> 		antennaBoundary_;
    casacore::uInt 				numAntennaBoundary_;
    casacore::Vector<casacore::Double> 		pointingTimeUTC_;
    casacore::Double 				lastTimeStamp_;
    casacore::Int 				lastAntennaIndex_;
    casacore::uInt 				pointingTableIndexCache_;
    PointingDirectionCalculator::MatrixShape 	shape_;

    // privatize  default constructor

    PointingDirectionCalculator();

    //**********************************************
    // CAS-8418 Spline Extended
    //  (Initial values on the top of Constructor)
    //**********************************************

    // Spline Type , Initialized in Constructor. 'true' enables Spline Interpoation. //
      bool useSplineInterpolation_ ;      

    // Current Spline Object (become active with specified Direction Column)
      casa::SplineInterpolation                     *currSpline_ ;   

    // Spline Object for each Direction-Column 
      casacore::vector<std::unique_ptr<casa::SplineInterpolation> >    splineObj_;

    // Internal conditions to check limitted service.  
      casacore::Vector<bool>                         initializeReady_  ;
      casacore::Vector<bool>                         coefficientReady_ ;

    // Accessor ID (See typedef above. ) 
      PtColID   accessorId_ ;
 
    // check specified Column when creating Spline-Object.

      bool checkColumn(casacore::MeasurementSet const &ms,
                        casacore::String const &columnName );

    // Initialize Coefficient table.
      bool initializeSplinefromPointingColumn(casacore::MeasurementSet const &ms, PtColID  colNo );

    // Current Spline-Object handle. (only available SplineInterpolation class) 
      casa::SplineInterpolation         *getCurrentSplineObj() {return currSpline_; }

};

//+
// CAS-8418
// Spline Interpolation Class
//-

class SplineInterpolation  {
public:
        // Coefficient table typedef //
          typedef casacore::Vector<casacore::Vector<casacore::Vector<casacore::Vector<casacore::Double> > > >
          COEFF;

        // Constructor 
          SplineInterpolation(casacore::MeasurementSet const &ms, PointingDirectionCalculator::ACCESSOR acc );

         ~SplineInterpolation() {    }

        // Calculating Function //

        casacore::Vector<casacore::Double>   calculate(casacore::uInt row,
                                                       casacore::Double dt,
                                                       casacore::uInt AntennaID =0);
        // Spline-Obj coefficient status   //

        bool isCoefficientReady()   {return stsCofficientReady; }
       
        // Programmers API:: for Coefficient Table access // 

        COEFF getCoeff() { return coeff_; }

        // CAS-8418 Time Gap
        bool isTimeGap(casacore::uInt ant, casacore::uInt index)
                      { return tmp_timegap[ant][index]; }

private:
        //  default constructor 

         SplineInterpolation();

        //  constructor sub. 

          void init( casacore::MeasurementSet const &ms, 
                     PointingDirectionCalculator::ACCESSOR const my_accessor);

        // Coefficient (set up by SDPosInterpolator)

          COEFF  coeff_;
       
        // Interal Staus (one Spline status)//
 
          bool stsCofficientReady = false;
  
        // Coeff debug in csv. //
          void dumpCsvCoeff();

        //*
        // CAS-8418 Time Gap
        //   (relocated and new )
        //*
        
         casacore::Vector<casacore::Vector<casacore::Vector<casacore::Double> > > tmp_dir;
         casacore::Vector<casacore::Vector<casacore::Double> >          tmp_time;
         casacore::Vector<casacore::Vector<casacore::Double> >          tmp_dtime;
         casacore::Vector<casacore::Vector<bool> >                      tmp_timegap;

};


} //# NAMESPACE CASA - END

#endif /* _SYNTHESIS_POINTING_DIRECTION_CALCULATOR_H_ */