//
// Access to service-engines (abstract methods) plus common functionality (merge):
// - search, cutout, mergefiles : abstract webservices funcs (overrride for AMQP, JNI)
// - merge : high level, which uses mergefiles after cutout

import java.util.logging.Logger;
import java.util.logging.Level;

import java.io.IOException;
import java.util.*; // ArrayList<String>
import java.io.File;
import nom.tam.fits.*;// Fits - for regridding



class Regrid
{
   private static final Logger LOGGER = Logger.getLogger(Regrid.class.getName());

   private Double average(ArrayList<Double> dd) {

      Double sum = 0.0;
      int sz = dd.size();

      for (int i=0; i < sz; i++) {
         sum += dd.get(i);
      }
      return sum / sz;
   }


   // returns number of non-degenerate axes in fitsfiles
   // this is utility for merge, where files must be homogenious,
   // so number of dimensions must be the same in
   // all files otherwise merge not possible
   //
   // Counts only non-degenerate axes (NAXISi >1).
   //
   // in:  fitsfiles : list of filenames with full path !?
   // out: int       : number of (non-degenerate) dimensions common to all fitsfiles
   public int dimensions(final String[] fitsfiles)
      //throws FitsException, IOException
   {
      int dim = 0;

      ArrayList<Integer>   Vnaxis = new ArrayList<Integer>();

      for(String fitsfile : fitsfiles) {

         // check if empty filename (;; typed on input)
         if(fitsfile.length() == 0)
            continue;

         try {

            Fits f = new Fits(fitsfile);
            Integer naxis = f.getHDU(0).getHeader().getIntValue("NAXIS");

            Integer nx = 0; // naxis excluding degenerate axes
            for(int i=1; i<=naxis; i++){
               if( 1 < f.getHDU(0).getHeader().getIntValue("NAXIS" + i))
                  nx++;
            }
            Vnaxis.add(nx);

         } catch (FitsException e) {
            LOGGER.log(Level.SEVERE, "dimensions:",e);
         } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "dimensions:",e);
         }
      }
      // check that dimensions in all files match
      dim = Vnaxis.get(0);
      for( int ix = 1; ix < Vnaxis.size() ; ix++ ) {
         if (Vnaxis.get(ix) != dim) {
            // FIXME throw exception
            dim = 0;
         };
      }
      return dim;
   }

   // regrid variant 2:
   // CRPIX can be different in files to be merged -> we'll adjust shift CRPIX for common CRVAL
   // attempt regrid velocity if needed and possible
   // returns true: add msg to client: "Keywords CDLET3, CRVAL3
   //               were modified for merge, see header of the merged file."
   // returns false: do nothing
   //
   // use closest neighbour algorithm - changes only metadata
   // possible: if CDELT3 does not differ more then one pixel (=size CDELT3)
   //           between the files to be merged and
   //           NAXIS3 is exactly the same (in cutouts should be)
   // needed:   if CDELT3 & CRVAL3 are not (exactly) equal between the files to be merged
   //
   // WARN: assumes (as Montage does too) 3rd axis is velocity (compatible)
   public Boolean regrid_vel2(final String[] fitsfiles)
      //   Boolean regrid_vel2(final ArrayList<String> fitsfiles)
      //throws FitsException, IOException
   {
      ArrayList<Double> Vcrval = new ArrayList<Double>();
      ArrayList<Double> Vcrpix = new ArrayList<Double>();
      ArrayList<Double> Vcdelt = new ArrayList<Double>();
      ArrayList<Long>   Vnaxis = new ArrayList<Long>();

      //
      // 1, read needed keywords from files to be merged
      //
      for(String fitsfile : fitsfiles) {

         // check if empty filename (;; typed on input)
         if(fitsfile.length() == 0)
            continue;

         // read orig keyword values

         try {

            // FITS
            Fits f = new Fits(fitsfile);

            // we should check here that 3rd axis is velocity-compatible (freq etc)
            // for now, remains only assumption (also Montage only assumes and offers mTransform if it is not so)
            // String oldkwd = f.getHDU(0).getHeader().getStringValue("CTYPE3");

            // get card values as string (for exact match comparison to avoid string -> double conversion artifacts)
            // String allcardstr   = f.getHDU(0).getHeader().findKey("CRVAL3");
            // String cardvaluestr = f.getHDU(0).getHeader().findCard("CRVAL3").getValue();
            // LOGGER.info("CRVAL3 as string: " + cardvaluestr);

            Vcrval.add(f.getHDU(0).getHeader().getDoubleValue("CRVAL3"));
            Vcrpix.add(f.getHDU(0).getHeader().getDoubleValue("CRPIX3"));
            Vcdelt.add(f.getHDU(0).getHeader().getDoubleValue("CDELT3"));
            Vnaxis.add(f.getHDU(0).getHeader().getLongValue("NAXIS3"));


         } catch (FitsException e) {
            LOGGER.log(Level.SEVERE, "regrid_vel2:",e);
         } catch (IOException e) {
            LOGGER.log(Level.SEVERE, "regrid_vel2:",e);
         }

      }
      /*/ debug print
        for( int ix = 0; ix < Vcrval.size() ; ix++ ) {

        LOGGER.info(ix +
        " " + Vcrval.get(ix) +
        " " + Vcdelt.get(ix) +
        " " + Vcrpix.get(ix) +
        " " + Vnaxis.get(ix)
        );

        }
        */

      //
      // 2, check if closeste-neighbour interpolation possible/needed:
      // NAXIS3 must match
      // max diff(CDELT3) << absvalue(CDELT3)
      // max diff(CRVAL3) << absvalue(CDELT3)
      //
      long dnaxis = Collections.max(Vnaxis) - Collections.min(Vnaxis);
      //LOGGER.info("dNAXIS : " + dnaxis);
      if( dnaxis != 0 ) {
         return false;
      }
      double minCDELT = Collections.min(Vcdelt);
      double maxCDELT = Collections.max(Vcdelt);

      double avgCDELT    = average(Vcdelt);
      double absavgCDELT = java.lang.Math.abs(avgCDELT);

      // FIXME use exceptions instead...
      if(absavgCDELT == 0.0) {
         LOGGER.warning("regrid: avg(CDELT3) == 0");
         return false;
      }

      double dcdelt = java.lang.Math.abs(maxCDELT - minCDELT);
      //LOGGER.info("dCDELT : " + dcdelt
      //                   + " ratio: " +
      //                   String.format("%.1f",100.0*dcdelt/absavgCDELT)
      //                   + " %" );
      if(dcdelt > absavgCDELT) {
         return false;
      }

      double minCRVAL = Collections.min(Vcrval);
      double maxCRVAL = Collections.max(Vcrval);
      double dcrval = java.lang.Math.abs(maxCRVAL - minCRVAL);
      //LOGGER.info("dCRVAL : " + dcrval + "|CDELT| : " + absavgCDELT
      //                   + " ratio: " +
      //                   String.format("%.1f",100.0*dcrval/absavgCDELT)
      //                   + " %" );
      //            if(dcrval > absavgCDELT) {
      //                return false;
      //            }
      // if we've got here all conditions for interpolation satisfied

      // exact match, interpolation not needed
      // ?? FIXME would be better check exact match by comparing card values as strings ??
      // to avoid string -> double conversion machine architecture dependencies (any?)
      if((dcrval == 0.0) && (dcdelt == 0.0)){
         return false;
      }

      //
      // 3, interpolation possible and needed: update fits file headers
      //    with new values
      //

      // interpolate closest neighbour: simply set the grid to average of all
      // they differ by less then a pixel (=CDELT3)
      double newCDELT = avgCDELT;        
      double newCRVAL = average(Vcrval);

      for(String fitsfile : fitsfiles) {

         // check if empty filename (;; typed on input)
         if(fitsfile.length() == 0)
            continue;

         try {

            Fits f = new Fits(fitsfile);

            double origCDELT = f.getHDU(0).getHeader().getDoubleValue("CDELT3");
            String commentCDELT = "VLKB OrigVal: " + origCDELT;
            f.getHDU(0).getHeader().addValue("CDELT3",newCDELT, commentCDELT);

            double origCRVAL = f.getHDU(0).getHeader().getDoubleValue("CRVAL3");
            String commentCRVAL = "VLKB OrigVal: " + origCRVAL;
            f.getHDU(0).getHeader().addValue("CRVAL3",newCRVAL,commentCRVAL);

            double origCRPIX = f.getHDU(0).getHeader().getDoubleValue("CRPIX3");
            String commentCRPIX = "VLKB OrigVal: " + origCRPIX;
            double newCRPIX = origCRPIX - ((newCRVAL - origCRVAL) / newCDELT);
            f.getHDU(0).getHeader().addValue("CRPIX3",newCRPIX,commentCRPIX);

            f.getHDU(0).rewrite();

         } catch(Exception e) {
            // FIXME do error handling properly...
            LOGGER.log(Level.SEVERE, "regrid_vel2:", e);
         }

      }

      return true;
   }

}

