// Copyright 2021 - Unistra/CNRS
// The MOC API project is distributed under the terms
// of the GNU General Public License version 3.
//
//This file is part of MOC API java project.
//
//    MOC API java project is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, version 3 of the License.
//
//    MOC API java project 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 General Public License for more details.
//
//    The GNU General Public License is available in COPYING file
//    along with MOC API java project.
//

package cds.moc;

import java.io.InputStream;
import java.io.OutputStream;


/**
 * The SFMoc class implements the methods specific to spatial frequency MOCs.
 * See:  IVOA MOC 2.0 standard => https://www.ivoa.net/documents/MOC/
 * @author Pierre Fernique [CDS]
 * @version 1.0 - February 2023 - creation
 *
 */
public class SFMoc extends Moc2D {
   
   /** SFMoc creator */
   public SFMoc() {
      super( new FMoc(), new SMoc() );
   }
   
   /** SFMoc creator
    * @param freqOrder MocOrder for frequency dimension [0..59]
    * @param spaceOrder MocOrder for space dimension [0..29]
    */
   public SFMoc( int freqOrder, int spaceOrder) throws Exception {
      this();
      setMocOrder(freqOrder, spaceOrder);
   }
   
  /** SFMoc creator
    * @param s String containing a SFMOC (ASCII, JSON)
    */  
   public SFMoc( String s ) throws Exception { 
      this();
      add( s );
   }
   
   /** SFMoc creator
    * @param in Input stream containing a SFMOC (ASCII, JSON or FITS)
    */
   public SFMoc( InputStream in ) throws Exception { 
      this();
      read(in);
   }
   
   /** SFMoc creator from one FMOC and one SMOC
    * @param fmoc
    * @param smoc
    * @throws Exception
    */
   public SFMoc( FMoc fmoc, SMoc smoc) throws Exception {
      this();
      init( fmoc, smoc);
   }
   
   /** SFMoc view from a SMoc
    * By associating a full freqency range to the space coverage 
    * @param moc the SMoc
    * @param freqOrder the freaqOrder for the frequency dimension
    */
   private SFMoc( SMoc moc, int freqOrder ) throws Exception {
      this( freqOrder, moc.getMocOrder() );
      initDim2(moc);
   }
   
   /** SFMoc view from a FMoc
    * By associating a full space range for each frequency ranges
    * @param moc the TMoc
    * @param spaceOrder the spaceOrder for the space dimension
    */
    private SFMoc( FMoc moc, int spaceOrder ) throws Exception {
      this( moc.getMocOrder(), spaceOrder );
      initDim1(moc);
//      if( !moc.isEmpty() ) {
//         Range2 r = new Range2( moc.range.sz );
//         SMoc allsky = new SMoc("0/0-11");
//         for( int i=0; i<moc.range.sz; i+=2 ) {
//            r.append(moc.range.r[i], moc.range.r[i+1], allsky.seeRangeList() );
//         }
//         setRangeList( r );
//      }
   }
    
   /** Create a SFMoc from any kind of MOC by adding full space or frequenct coverage if required 
    * WARNING: it is not a copy, may share data from the original Moc
    */
   static protected SFMoc asSFMoc(Moc moc) throws Exception {
      moc.flush();
      if( moc instanceof SFMoc )  return (SFMoc) moc;
      if( moc instanceof FMoc )   return new SFMoc( (FMoc)moc, moc.getSpaceOrder() );
      if( moc instanceof SMoc )   return new SFMoc( (SMoc)moc, moc.getFreqOrder() );
      throw new Exception("No frequency or space dimension");
   }

   
   /** Clone Moc (deep copy) */
   public SFMoc clone() throws CloneNotSupportedException {
      SFMoc moc = dup();
      clone1( moc );
      return moc;
   }
   
   /** Deep copy. The source is this, the target is the Moc in parameter */
   protected void clone1( Moc moc ) throws CloneNotSupportedException {
      if( !(moc instanceof SFMoc) ) throw new CloneNotSupportedException("Uncompatible type of MOC for clone. Must be SFMoc");
      super.clone1( moc );
   }
   
   /** Create and instance of same class, same sys, but no data nor mocorder */
   public SFMoc dup() { 
      SFMoc moc = new SFMoc();
      moc.protoDim1.sys=protoDim1.sys;
      moc.protoDim2.sys=protoDim2.sys;
      return moc;
   }
   
   /************************************ SFMoc specifical methods *****************************************/
   
   
   public void add(HealpixImpl healpix,double alpha, double delta, double freqMin, double freqMax) throws Exception {
      long smin = healpix.ang2pix(SMoc.MAXORD_S, alpha, delta);
      long fmin = Double.isNaN(freqMin) ? 0L : FMoc.getHash(freqMin);
      long fmax = Double.isNaN(freqMax) ? FMoc.NBVAL_F : FMoc.getHash(freqMax);
      add(fmin,fmax,smin,smin);
   }

   /** Adding one élément by spaceOrder/npix et [jdmin..jdmax] */
   public void add(int order, long npix, double freqMin, double freqMax) throws Exception  {
      long smin = getStart2(order,npix);
      long smax = getEnd2(order,npix)-1L;
      long fmin = Double.isNaN(freqMin) ? 0L : FMoc.getHash(freqMin);
      long fmax = Double.isNaN(freqMax) ? FMoc.NBVAL_F : FMoc.getHash(freqMax);
      add( fmin, fmax, smin, smax );
   }
   
   public void add(long fmin, long fmax, long smin, long smax) throws Exception  {
      add( FMoc.MAXORD_F, fmin, fmax, SMoc.MAXORD_S, smin, smax );
   }
   
   public void add( double freqMin, double freqMax, SMoc smoc) throws Exception  {
      long fmin = Double.isNaN(freqMin) ? 0L : FMoc.getHash(freqMin);
      long fmax = Double.isNaN(freqMax) ? FMoc.NBVAL_F : FMoc.getHash(freqMax);
      add(fmin,fmax, new Range( smoc.seeRangeList() ) );
   }
   
   /** Set time order [0..59] */
   public void setFreqOrder( int freqOrder ) throws Exception { setMocOrder1( freqOrder ); }
   
   /** Set space order [0..29] */
   public void setSpaceOrder( int spaceOrder ) throws Exception { setMocOrder2( spaceOrder ); }
   
   /** Get Freq order */
   public int getFreqOrder()  { return getMocOrder1(); }
   
   /** Get space order */
   public int getSpaceOrder() { return getMocOrder2(); }
   
   /** Set alternative Coosys. All celestial SMoc must be expressed in ICRS (see IVOA MOC 2.0)
    * but alternative is possible for other sphere coverage notably the planets
    * @param coosys alternative coosys keyword (not standardized in IVOA document)
    */
   public void setSpaceSys( String coosys ) { protoDim2.setSys( coosys ); }
   
   /** Get the Coosys. See setSpaceSys() */
   public String getSpaceSys() { return protoDim2.getSys(); }
   
   /** Return minimal frequency - -1 if empty*/
   public double getFreqMin() throws Exception {
      if( isEmpty() ) return -1;
      return FMoc.getFreq( range.begins(0) );
   }

   /** Return maximal frequency -1 if empty*/
   public double getTimeMax() throws Exception {
      if( isEmpty() ) return -1;
      return FMoc.getFreq( range.ends( range.nranges()-1 ) );
   }
   
   public int getFreqRanges() { return getNbRanges(); }
   
   /** FMoc covering from the whole SFMOC */
   public FMoc getFreqMoc() throws Exception {
      FMoc moc = new FMoc( getFreqOrder() );
      moc.setRangeList( new Range(range) );
      return moc;
   }

   /** FMoc from the intersection with the spaceMoc */
   public FMoc getFreqMoc( SMoc spaceMoc) throws Exception {
      if( spaceMoc==null || spaceMoc.isEmpty() ) return getFreqMoc();
      FMoc moc = new FMoc( getFreqOrder() );
      Range r1 = new Range();
      
      for( int i=0; i<range.sz; i+=2 ) {
         Range m = range.rr[i>>>1];
         if( spaceMoc.range.overlaps(m) ) r1.append( range.r[i], range.r[i+1] );
      }
      
      moc.range = r1;
      return moc;
   }
   
   /** SMoc covering the whole SFMOC */
   public SMoc getSpaceMoc() throws Exception {
      return getSpaceMoc(-1,Long.MAX_VALUE);
   }
   

   /** SMoc extraction from a frequency range
    * @param fmin  min of range (order 59)
    * @param fmax max of range (included - order 59)
    */
   public SMoc getSpaceMoc(long fmin,long fmax) throws Exception {
      if( fmin>fmax ) throw new Exception("bad frequency range");

      // SFMOC vide => SMOC vide
      if( range.sz==0 ) {
         SMoc moc = new SMoc( getSpaceOrder() );
         moc.setSpaceSys( getSpaceSys() );
         return moc;
      }
      
      // Global ? use a cache
      boolean isSFull = fmin<=range.r[0] && fmax>=range.r[ range.sz-1 ];
      if( isSFull ) {
         
         // Un seul intervalle frequentiel => rien à agréger
         if( range.sz==2 ) {
            SMoc moc = new SMoc( getSpaceOrder() );
            moc.setSpaceSys( getSpaceSys() );
            moc.setRangeList(range.rr[0]);
            return moc;
         }
         
         // Déja calculé et disponible en cache ?
         if( cacheDim2Full!=null ) return (SMoc) cacheDim2Full;
      }
      
      int pos = range.indexOf(fmin);
      if( (pos&1)==1 ) {
         if( pos<0 ) pos++;
         else pos--;
      }
      
//      long t0 = System.currentTimeMillis();
      
      SMoc moc = new SMoc( getSpaceOrder() );
      moc.setSpaceSys( getSpaceSys() );
      moc.bufferOn(2000000);
      for( int i=pos; i<range.sz; i+=2 ) {
         if( range.r[i]>fmax ) break;
         Range m = range.rr[i>>>1];
         for( int j=0; j<m.sz; j+=2 ) moc.add(SMoc.MAXORD_S, m.r[j], m.r[j+1]-1L );
      }
      moc.bufferOff();
//      long dt = System.currentTimeMillis()-t0;
      
      // Mémorisation en cache si le calcul est trop lent
      if( isSFull /* && dt>1 */ ) {
//         System.out.println("getSpaceMoc in "+dt+"ms");
         cacheDim2Full=moc;
      }
      
      return moc;
   }

   /** True if the npix (deepest level) and freq is in the SFMoc */
   public  boolean contains(long npix, double freq) throws Exception {
      long hash = FMoc.getHash(freq);
      int i = range.indexOf(hash);
      if( (i&1)!=0 ) return false;
      return range.rr[i/2].contains(npix);
   }

   /***************************************************** Operations ******************************************/
   
   public boolean isIncluding(Moc moc) throws Exception { 
      if( moc instanceof SMoc ) return getSpaceMoc().isIncluding(moc);
      if( moc instanceof FMoc ) return getFreqMoc().isIncluding(moc);
      if( !(moc instanceof SFMoc) ) throw new Exception("no frequency or space dimension");
      flush();
      return range.contains( moc.seeRangeList() );
   }   
   
   public boolean isIntersecting(Moc moc) throws Exception { 
      if( moc instanceof SMoc ) return getSpaceMoc().isIntersecting(moc);
      if( moc instanceof FMoc ) return getFreqMoc().isIntersecting(moc);
      if( !(moc instanceof SFMoc) ) throw new Exception("no frequency or space dimension");
      flush(); 
      return range.overlaps( moc.seeRangeList() );
   }
   
   /** Return the Union with another Moc */
   public SFMoc union(Moc moc) throws Exception {
      return (SFMoc) super.union( asSFMoc(moc));
   }

   /** Return the subtraction with another Moc */
   public SFMoc subtraction(Moc moc) throws Exception {
      return (SFMoc) super.subtraction( asSFMoc(moc) );
   }
   
   /** Return the Intersection with another Moc */
   public SFMoc intersection(Moc moc) throws Exception {
      return (SFMoc) super.intersection( asSFMoc(moc) );
   }
   
   /** Return the complement */
   public SFMoc complement() throws Exception {
      SFMoc moc = new SFMoc(  getFreqOrder() , getSpaceOrder());
      moc.add("f0/0 s0/0-11");
      return moc.subtraction(this);
   }

   /*************************************************************** I/O *****************************************************/

   /** Write specifical SFMOC properties  */
   protected int writeSpecificFitsProp( OutputStream out  ) throws Exception {
      int n=0;
      out.write( getFitsLine("MOCDIM","FREQUENCY.SPACE","SFMOC: Frequency dimension first, ") );  n+=80;      
      out.write( getFitsLine("ORDERING","RANGE","Range coding") );                                n+=80;      
      out.write( getFitsLine("MOCORD_F",""+ getFreqOrder(),"Frequency MOC resolution") );         n+=80;      
      out.write( getFitsLine("MOCORD_S",""+ getSpaceOrder(),"Space MOC resolution") );            n+=80;      
      out.write( getFitsLine("COORDSYS",getSpaceSys(),"Space reference frame") );                 n+=80;
      return n;
   }
   
   protected long codeDim1(long a) { return codeFreq(a); };


   /** Internal method: read FITS data according to the type of MOC.
    * @param in The input stream
    * @param naxis1 size of FITS row (in bytes) (generally ==nbyte, but may be 1024 for buffering)
    * @param naxis2 number of values
    * @param nbyte size of each value (in bytes)
    * @param header HDU1 header
    * @throws Exception
    */
   protected void readSpecificData( InputStream in, int naxis1, int naxis2, int nbyte, HeaderFits header) throws Exception {
      
      int freqOrder=-1,spaceOrder=-1;

      String type = header.getStringFromHeader("MOCDIM");
      if( type!=null ) {
         freqOrder  = header.getIntFromHeader("MOCORD_F");
         spaceOrder = header.getIntFromHeader("MOCORD_S");
      }  
      
      setFreqOrder(freqOrder);
      setSpaceOrder(spaceOrder);
      
      byte [] buf = new byte[naxis1*naxis2];
      readFully(in,buf);
      readSpecificDataRange((naxis1*naxis2)/nbyte,buf,RAW);
   }
   
   protected boolean isCodedDim1(long a) { return isCodedFreq(a); }
   protected long decodeDim1(long a) { return decodeFreq(a); }

}
