//# FluxStandard.cc: Implementation of FluxStandard.h
//# Copyright (C) 1996,1997,1999,2001,2002
//# 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: FluxStandard.cc 21292 2012-11-28 14:58:19Z gervandiepen $
//----------------------------------------------------------------------------

#include <components/ComponentModels/FluxStandard.h>
//#include <components/ComponentModels/FluxStdsQS.h>
#include <components/ComponentModels/FluxStdsQS2.h>
#include <components/ComponentModels/FluxCalc_SS_JPL_Butler.h>
#include <components/ComponentModels/ComponentType.h>
#include <components/ComponentModels/ComponentList.h>
#include <components/ComponentModels/SkyComponent.h>
#include <components/ComponentModels/ConstantSpectrum.h>
#include <components/ComponentModels/TabularSpectrum.h>
#include <components/ComponentModels/PointShape.h>
#include <components/ComponentModels/DiskShape.h>
#include <casacore/casa/BasicMath/Math.h>
#include <casacore/casa/BasicSL/String.h>
#include <casacore/casa/BasicSL/Constants.h>
#include <casacore/casa/Logging/LogIO.h>
#include <casacore/casa/OS/File.h>
#include <casacore/casa/OS/Path.h>
#include <casacore/casa/Utilities/CountedPtr.h>
#include <sstream>
#include <iomanip>
#include <casacore/measures/Measures/MDirection.h>
#include <casacore/measures/Measures/MCDirection.h>
#include <casacore/measures/Measures/MeasConvert.h>
#include <casacore/measures/Measures/MEpoch.h>
#include <casacore/measures/Measures/MFrequency.h>
#include <casacore/measures/Measures/MeasTable.h>

using namespace casacore;
namespace casa { //# NAMESPACE CASA - BEGIN

//----------------------------------------------------------------------------

FluxStandard::FluxStandard(const FluxStandard::FluxScale scale) : 
  itsFluxScale(scale),
  has_direction_p(false),
  interpmethod_p("")
{
// Default constructor
// Output to private data:
//    itsFluxScale      FluxStandard::FluxScale     Flux scale (eg. BAARS)
//
}

//----------------------------------------------------------------------------

FluxStandard::~FluxStandard()
{
// Default destructor
//
}

//----------------------------------------------------------------------------

Bool FluxStandard::compute (const String& sourceName, 
                            const MDirection& sourceDir,
                            const MFrequency& mfreq,
                            const MEpoch& mtime,
			    Flux <Double>& value, Flux <Double>& error)
{
  // I refuse to duplicate the monstrosity below to skip a short for loop.
  Vector<Flux<Double> > fluxes(1);
  Vector<Flux<Double> > errors(1);
  Vector<MFrequency> mfreqs(1);
  
  mfreqs[0] = mfreq;
  Bool success = compute(sourceName, sourceDir, mfreqs, mtime, fluxes, errors);
  
  value = fluxes[0];
  error = errors[0];
  return success;
}

Bool FluxStandard::compute(const String& sourceName, 
                           const MDirection& sourceDir,
                           const Vector<Vector<MFrequency> >& mfreqs,
                           const MEpoch& mtime,
                           Vector<Vector<Flux<Double> > >& values,
                           Vector<Vector<Flux<Double> > >& errors)
{
  Bool success = true;
  uInt nspws = mfreqs.nelements();

  for(uInt spw = 0; spw < nspws; ++spw)
    success &= compute(sourceName, sourceDir, mfreqs[spw], mtime, values[spw], errors[spw],
                       spw == 0);

  return success;
}

Bool FluxStandard::compute(const String& sourceName, 
                           const MDirection& sourceDir,
                           const Vector<MFrequency>& mfreqs,
                           const MEpoch& mtime,
                           Vector<Flux<Double> >& values,
                           Vector<Flux<Double> >& errors,
                           const Bool verbose)
{
// Compute the flux density for a specified source at a specified set of
// frequencies.
// Inputs:
//    sourceName  Source name
//    mfreqs      Desired frequencies
//    mtime       Desired time 
// Output:
//    values      Computed total flux densities
//    errors      Flux density uncertainties; 0 => not known.
//    compute     false if sourceName is not recognized
//                as a standard reference.
//
  LogIO os(LogOrigin("FluxStandard", "compute"));

  // There used to be a big
  // if(string == 'dfa" || string == "bla" || ...)
  // ...else if...else... 
  // chain here, with a similar
  // switch(standard) statement inside each if clause.  Finally, inside each
  // switch case came the actual calculation, usually a 2nd or 3rd order
  // polynomial in log(freq).
  //
  // This meant that the chain of string comparisons and case selections,
  // typically more expensive than the actual calculation, was repeated for
  // each frequency.  It would have been better to do the source and standard
  // determination at the start, and then do the calculation like
  // ans = coeffs[std][src][0] + lf * (coeffs[std][src][1] + lf * ...),
  // but:
  //   * std and src would naturally be enums, and thus not quite 
  //     natural indices for an array.
  //   * The standards do not necessarily use the same set, or number,
  //     of sources.
  // Both of those could be gotten around by using a std::map, but then
  // accessing the coeffs entails a function call.  Also, it ties the
  // functional form of the standards to a low-order polynomial in
  // log(freq).
  //
  // If a function call will be used anyway, why not use a functor to cache the
  // (std, src) state?  It is more convenient than adding a loop over
  // log10(frequency) inside each switch case.

  //CountedPtr<FluxCalcQS> fluxStdPtr;
  CountedPtr<FluxCalcVQS> fluxStdPtr;
  Bool timeVariable(false);
  if(itsFluxScale == BAARS)
    fluxStdPtr = new NSTDS::FluxStdBaars;
  else if(itsFluxScale == PERLEY_90)
    fluxStdPtr = new NSTDS::FluxStdPerley90;
  else if(itsFluxScale == PERLEY_TAYLOR_95)
    fluxStdPtr = new NSTDS::FluxStdPerleyTaylor95;
  else if(itsFluxScale == PERLEY_TAYLOR_99)
    fluxStdPtr = new NSTDS::FluxStdPerleyTaylor99;
  else if(itsFluxScale == PERLEY_BUTLER_2010)
    fluxStdPtr = new NSTDS::FluxStdPerleyButler2010;
  else if(itsFluxScale == PERLEY_BUTLER_2013) {
    fluxStdPtr = new NSTDS::FluxStdPerleyButler2013;
    timeVariable=true; // to read from the table 
    if (interpmethod_p=="") {
      ostringstream oss;
      oss << "Unset interpmethod. Please set the method first";
      throw(AipsError(String(oss)));
    }
  }
  else if(itsFluxScale == SCAIFE_HEALD_2012)
    fluxStdPtr = new NSTDS::FluxStdScaifeHeald2012;
  else if(itsFluxScale == STEVENS_REYNOLDS_2016)
    fluxStdPtr = new NSTDS::FluxStdStevensReynolds2016;
  else if(itsFluxScale == PERLEY_BUTLER_2017) {
    fluxStdPtr = new NSTDS::FluxStdPerleyButler2017;
    timeVariable=true; // to read from the table 
    if (interpmethod_p=="") {
      ostringstream oss;
      oss << "Unset interpmethod. Please set the method first";
      throw(AipsError(String(oss)));
    }
  }
  else{
    if(verbose)
      os << LogIO::SEVERE
         << "Flux standard " << standardName(itsFluxScale)
         << " cannot be used this way.  (Does it require a time?)"
         << LogIO::POST;
    return false;
  }
  os << LogIO::DEBUG1
     << "Using flux standard: " << standardName(itsFluxScale)
     << LogIO::POST;

  // Set the source or fail.
  // setSource needs J2000 coordinates for source matching by position
  MDirection sourceDirJ2000;
  if (sourceDir.getRefString()!="J2000") {
    MeasFrame frame(mtime);
    MDirection::Ref outref(MDirection::J2000, frame);
    sourceDirJ2000 = MDirection::Convert(sourceDir, outref)(); 
  }
  else {
    sourceDirJ2000 = sourceDir;
  }   
  if(!fluxStdPtr->setSource(sourceName,sourceDirJ2000)){
    if(verbose)
      os << LogIO::SEVERE
         << sourceName << " is not recognized by " << standardName(itsFluxScale)
         << LogIO::POST;
    // TODO?: Look for another standard that does recognize sourceName?
    return false;
  }
  else{
    direction_p = fluxStdPtr->getDirection();
    has_direction_p = true;
  }
  // Compute the flux density values and their uncertainties, returning whether
  // or not it worked.
  if (timeVariable) return (*fluxStdPtr)(values,errors,mfreqs,mtime,interpmethod_p);
  return (*fluxStdPtr)(values, errors, mfreqs);
}

// Like compute, but it also saves a ComponentList for the source to disk
// and returns the name (sourceName_mfreqGHzmtime.cl), making it suitable for
// resolved sources.
// mtime is ignored for nonvariable objects.
// Solar System objects are typically resolved and variable!
//
// Currently each component "list" only has 1 or 0 components.
//
// Inputs:
//    sourceName  const String&              Source name
//    mfreqs      const Vector<MFrequency>&  Desired frequencies
//    mtime       const MEpoch&              Desired time
// Output:
//    values      Vector<Flux>&              Computed total flux densities
//    errors      Vector<Flux>&              Flux density errors;
//                                           (0 => unknown).
//    clnames     Vector<String>&            Pathnames of the
//                                           ComponentLists.  "" if no
//                                           components were made.
//
Bool FluxStandard::computeCL(const String& sourceName,
                             const Vector<Vector<MFrequency> >& mfreqs,
                             const MEpoch& mtime, const MDirection& position,
                             Vector<Vector<Flux<Double> > >& values,
                             Vector<Vector<Flux<Double> > >& errors,
                             Vector<String>& clpaths,
			     const String& prefix)
{
  LogIO os(LogOrigin("FluxStandard", "computeCL"));
  uInt nspws = mfreqs.nelements();
  Bool success = false;

  if(itsFluxScale < FluxStandard::HAS_RESOLUTION_INFO){
    if(this->compute(sourceName, position, mfreqs, mtime, values, errors)){
      // Create a point component with the specified flux density.
      MDirection dummy;
      PointShape point(position.getValue().separation(dummy.getValue()) < 1e-7 &&
		       position.getRef() == dummy.getRef() ? direction_p : position);

      for(uInt spw = 0; spw < nspws; ++spw){
        clpaths[spw] = makeComponentList(sourceName, mfreqs[spw], mtime,
                                         values[spw], point,
					 prefix + "spw" + String::toString(spw) + "_");
      }
      success = true;
    }
  }
  else if(itsFluxScale == FluxStandard::SS_JPL_BUTLER){
    FluxCalc_SS_JPL_Butler ssobj(sourceName, mtime);
    Double angdiam;

    for(uInt spw = 0; spw < nspws; ++spw){
      ComponentType::Shape cmpshape = ssobj.compute(values[spw], errors[spw], angdiam,
                                                    mfreqs[spw], spw == 0);
    
      switch(cmpshape){
      case ComponentType::DISK:
        {
          // Create a uniform disk component with the specified flux density.
	  MDirection dummy;
          DiskShape disk;

          // Should we worry about tracking position?
          disk.setRefDirection(position.getValue().separation(dummy.getValue()) < 1e-7 &&
			       position.getRef() == dummy.getRef() ? ssobj.getDirection() :
			       position);

          disk.setWidthInRad(angdiam, angdiam, 0.0);

          clpaths[spw] = makeComponentList(sourceName, mfreqs[spw], mtime,
                                           values[spw], disk,
					   prefix + "spw" + String::toString(spw) + "_");
          success = true;
          break;
        }
      default: {
        ostringstream oss;

        oss << ComponentType::name(cmpshape) << " is not a supported component type.";
        throw(AipsError(String(oss)));
      }
      }
    }
  }
  return success;
}

void FluxStandard::setInterpMethod(const String& interpmethod)
{
  LogIO os(LogOrigin("FluxStandard", "setInterpMode"));
  if(interpmethod.contains("nearest")) {
    interpmethod_p = "nearestNeighbour";
  }
  else if(interpmethod.contains("linear")) {
    interpmethod_p = "linear";
  }
  else if(interpmethod.contains("cubic")) {
    interpmethod_p = "cubic"; 
  }
  else if(interpmethod.contains("spline")) {
    interpmethod_p = "spline";
  }
  else {
    ostringstream oss;
    oss << interpmethod << " is not a supported interpolation method";
    throw(AipsError(String(oss)));
  }  
}



String FluxStandard::makeComponentList(const String& sourceName,
                                       const Vector<MFrequency>& mfreqs,
                                       const MEpoch& mtime,
                                       const Vector<Flux<Double> >& values,
                                       const ComponentShape& cmp,
				       const String& prefix)
{
  LogIO os(LogOrigin("FluxStandard", "makeComponentList"));
  uInt nchans = mfreqs.nelements();

  if(nchans > 1){
    Vector<MVFrequency> freqvals(nchans);

    for(uInt c = 0; c < nchans; ++c)
      freqvals[c] = mfreqs[c].getValue();

    TabularSpectrum ts(mfreqs[0], freqvals, values, mfreqs[0].getRef());
          
    return makeComponentList(sourceName, mfreqs[0], mtime,
                             values[0], cmp, ts, prefix);
  }
  else{
    ConstantSpectrum cspectrum;

    return makeComponentList(sourceName, mfreqs[0], mtime,
                             values[0], cmp, cspectrum, prefix);
  }
}

String FluxStandard::makeComponentList(const String& sourceName,
                                       const MFrequency& mfreq,
                                       const MEpoch& mtime,
                                       const Flux<Double>& fluxval,
                                       const ComponentShape& cmp,
                                       const SpectralModel& spectrum,
				       const String& prefix)
{
  LogIO os(LogOrigin("FluxStandard", "makeComponentList"));

  // Make up the ComponentList's pathname.
  ostringstream oss;
  oss << prefix << sourceName << "_" //<< setprecision(1)
      << mfreq.get("GHz").getValue() << "GHz";
  //  String datetime;  // to nearest minute.
  oss << mtime.get("d").getValue() << "d.cl";
  String clpath(oss);
  // allow space as a part of the path
  //uInt nspaces = clpath.gsub(" ", "_");

  os << LogIO::DEBUG1
     << "sourceName: " << sourceName
     << "\nmfreq: " << mfreq.get("GHz").getValue() << "GHz"
     << "\nmtime: " << mtime.get("d").getValue() << "d"
   //  << "\nclpath: " << clpath << " (replaced " << nspaces
    // << " spaces)"
     << LogIO::POST;

  // If clpath already exists on disk, assume our work here is done, and don't
  // try to redo it.  It's not just laziness - it avoids collisions.
  // This happens when a continuum spw has the same center freq as a spectral
  // spw that is not being scaled by channel.
  File testExistence(clpath);
  if(!testExistence.isDirectory()){
    // Create a component list containing cmp, and force a call to its d'tor
    // using scoping rules.
    ComponentList cl;
    SkyComponent skycomp(fluxval, cmp, spectrum);
	
    cl.add(skycomp);
    // Since fluxval is given for mfreq, it should update the reference frequency
    // set via SpectralIndex (TT)
    Int ncl=cl.nelements();
    Vector<Int> which(1);
    which(0) = ncl-1;
    MVFrequency mvfreq = MVFrequency(mfreq.get("Hz").getValue());
    cl.setRefFrequency(which,mvfreq);
    cl.rename(clpath, Table::New);
  }
  return clpath;
}

//----------------------------------------------------------------------------

Bool FluxStandard::matchStandard (const String& name, 
				  FluxStandard::FluxScale& stdEnum,
				  String& stdName)
{
// Match an input string to a standard/catalog enum and descriptor
// Inputs:
//    name             const String&             Input string
// Output:
//    stdEnum          FluxStandard::FluxScale   Matching enum
//    stdName          String                    Standard descriptor for 
//                                               the matching enum.
//    matchStandard    Bool                      true if matched; false
//                                               if default returned.
//
  // Set default enum
  stdEnum = FluxStandard::PERLEY_TAYLOR_99;
  //  stdEnum = FluxStandard::PERLEY_BUTLER_2010;   // Not yet!

  // Local lowercase copy of input string
  String lname = name;
  lname.downcase();
  Bool matched = true;

  // Case input string match of:
  //
  // Perley (1990)
  if (lname.contains("perley") && 
      (lname.contains("90") || lname.contains("1990"))) {
    stdEnum = FluxStandard::PERLEY_90;
  }
  // Perley-Taylor (1995)
  else if (lname.contains("perley") && lname.contains("taylor") &&
      (lname.contains("95") || lname.contains("1995"))) {
    stdEnum = FluxStandard::PERLEY_TAYLOR_95;
  }
  // Perley-Taylor (1999)
  else if (lname.contains("perley") && lname.contains("taylor") &&
      (lname.contains("99") || lname.contains("1999"))) {
    stdEnum = FluxStandard::PERLEY_TAYLOR_99;
  }
  // Perley-Butler (2010)
  else if (lname.contains("perley") && lname.contains("butler") &&
      (lname.contains("10") || lname.contains("2010"))) {
    stdEnum = FluxStandard::PERLEY_BUTLER_2010;
  }
  // Perley-Butler (2013)
  else if (lname.contains("perley") && lname.contains("butler") &&
      (lname.contains("13") || lname.contains("2013"))) {
    stdEnum = FluxStandard::PERLEY_BUTLER_2013;
  }
  // Perley-Butler (2017)
  else if (lname.contains("perley") && lname.contains("butler") &&
      (lname.contains("17") || lname.contains("2017"))) {
    stdEnum = FluxStandard::PERLEY_BUTLER_2017;
  }
  // Scaife & Heald (2012)
  else if (lname.contains("scaife") && lname.contains("heald") &&
      (lname.contains("12") || lname.contains("2012"))) {
    stdEnum = FluxStandard::SCAIFE_HEALD_2012;
  }
  // Stevens-Reynolds (2016)
  else if (lname.contains("stevens") && lname.contains("reynolds") &&
        (lname.contains("16") || lname.contains("2016"))) {
    stdEnum = FluxStandard::STEVENS_REYNOLDS_2016;
  }
  // Baars
  else if (lname.contains("baars")) {
    stdEnum = FluxStandard::BAARS;
  }
  else if(lname.contains("jpl") || lname.contains("horizons")){
    stdEnum = FluxStandard::SS_JPL_BUTLER;
  }
  else
    matched = false;

  // Retrieve standard descriptor
  stdName = standardName (stdEnum);

  return matched;
}

//----------------------------------------------------------------------------

String FluxStandard::standardName (const FluxStandard::FluxScale& stdEnum)
{
// Return the standard descriptor for a specified standard/catalog enum
// Inputs:
//    stdEnum          FluxStandard::FluxScale   Standard/catalog enum
// Output:
//    standardName     String                    Standard descriptor
//
  // Case scale enum of:
  //
  String stdName;
  switch (stdEnum) {
  case BAARS: {
    stdName = "Baars";
    break;
  }
  case PERLEY_90: {
    stdName = "Perley 90";
    break;
  }
  case PERLEY_TAYLOR_95: {
    stdName = "Perley-Taylor 95";
    break;
  }
  case PERLEY_TAYLOR_99: {
    stdName = "Perley-Taylor 99";
    break;
  }
  case PERLEY_BUTLER_2010: {
    stdName = "Perley-Butler 2010";
    break;
  }
  case PERLEY_BUTLER_2013: {
    stdName = "Perley-Butler 2013";
    break;
  }
    case SCAIFE_HEALD_2012: {
    stdName = "Scaife-Heald 2012";
    break;
  }
  case PERLEY_BUTLER_2017: {
    stdName = "Perley-Butler 2017";
    break;
  }
    case STEVENS_REYNOLDS_2016: {
    stdName = "Stevens-Reynolds 2016";
    break;
  }
  case SS_JPL_BUTLER: 
    {
      stdName = "JPL-Butler Solar System Object";
      break;
    }
  default: 
    {
      stdName = "unrecognized standard";
    }
  }
  return stdName;
}

//----------------------------------------------------------------------------
// End of FluxStandard definition.
//----------------------------------------------------------------------------

} //# NAMESPACE CASA - END