// 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;
import java.util.InputMismatchException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * The FMoc class implements the methods specific to frequency MOCs.
 * It is based on Frequences mapping to long (FX method)
 * Frequency index (from 5.048709793414476e-29 to 5.846006549323611e+48 Hz)
 * @author Pierre Fernique [CDS]
 * @version 1.0 - Jul 2022 - creation
 *
 */
public class FMoc extends Moc1D {
   
   static public final double DAYMICROSEC = 86400000000.;

   static final public int  MAXORD_F = 59;      // Max order
   static final public int  FACT_F   = 2;       // Factor between two consecutive order
   static final public char DIM_F    = 'f';     // Char signature for FMoc
   static final public long NBVAL_F  = pow2( MAXORD_F );   //  // nb of cells at the deepest order
   static final public String SYS_F = "FREQUENCY";

   /** Return the deepest possible order (ex: 29 for SMoc, 61 for FMoc) */
   public final int  maxOrder()    { return MAXORD_F; }
   
   /** Return the number of bit shifting between two consecutive orders (ex: 2 for SMoc, 1 for FMoc) */
   public final int  shiftOrder()  { return FACT_F/2; }
   
   /** Return the Moc signature character (ex: 's' for SMoc, 'f' for FMOC) */
   public final char cDim()   { return DIM_F; }
   
   /** Return the number of values at the deepest order (ex: 2x2^29x2^29 for SMoc, 2^61 for FMoc) */
   public final long maxVal() { return NBVAL_F; }
   
   /** Return the default reference system */
   public final String sys() { return SYS_F; }


   public FMoc() { super(); }
   public FMoc( int mocOrder ) { super( mocOrder ); }
   public FMoc( String s ) throws Exception { super(s); }
   public FMoc( FMoc moc ) throws Exception { super( moc ); }
   public FMoc( InputStream in ) throws Exception { super(in); }
      
   /** Clone Moc (deep copy) */
   public FMoc clone() throws CloneNotSupportedException {
      FMoc 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 FMoc) ) throw new CloneNotSupportedException("Uncompatible type of MOC for clone. Must be TMoc");
      super.clone1( moc );
   }
   
   /** Create and instance of same class, same sys, but no data nor mocorder */
   public FMoc dup() {
      FMoc moc = new FMoc();
      moc.sys=sys;
      return moc;
   }
   
   /** Return the number of bytes used for coding each FITS value (8 for long) */
   public int sizeOfCoding() { return 8; }
   
   /** Return the number of values to write in FITS serialization */
   public int getNbCoding() { return range.sz; }
   
   /** Add directly a TMoc */
   public void add(Moc moc) throws Exception { 
      if( !(moc instanceof FMoc) ) throw new Exception("Uncompatible Moc for adding");
      super.add(moc);
   }
   
   /** Get the Timesys */
   public String getTimeSys() { return SYS_F; }

   /************************************ FMoc specifical methods  *****************************************/

   // Mask to keep only the f64 exponent part
   static final long F64_EXPONENT_BIT_MASK = 0x7FFL << 52;
   // Inverse of the f64 exponent mask
   static final long F64_BUT_EXPONENT_BIT_MASK = ~F64_EXPONENT_BIT_MASK;
   
//   static final long F64_SIGN_BIT_MASK = 0x8000000000000000L;
//   // Equals !F64_SIGN_BIT_MASK (the inverse of the f64 sign mask)
//   static final long F64_BUT_SIGN_BIT_MASK = 0x7FFFFFFFFFFFFFFFL;
//   // Mask to keep only the f64 mantissa part
//   static final long F64_MANTISSA_BIT_MASK = ~(0xFFFL << 52);
//   // Inverse of the f64 mantissa mask
//   static final long F64_BUT_MANTISSA_BIT_MASK = 0xFFFL << 52;
   
   static public final double FREQ_MIN = 5.048709793414476e-29; // f64::from_bits(929_u64 << 52);
   static public final double FREQ_MAX = 5.846006549323611e+48; // f64::from_bits((1184_u64 << 52) | F64_MANTISSA_BIT_MASK);
   static public final long LMIN = getHash1(FREQ_MIN);
   static public final long LMAX = getHash1(FREQ_MAX);
   
   static public long getHash(double freq) throws Exception {
      if( freq<FREQ_MIN || freq>FREQ_MAX ) throw new Exception("Frequency outside ["+FREQ_MIN+" .. "+FREQ_MAX+"]");
      return getHash1(freq);
   }
   static public long getHash1(double freq) {
      long freq_bits = Double.doubleToLongBits(freq);
      long exponent = (freq_bits & F64_EXPONENT_BIT_MASK) >>> 52;
      exponent = (exponent - 929) << 52;
      long hash = (freq_bits & F64_BUT_EXPONENT_BIT_MASK) | exponent;
      return hash;
   }
   
   static public double getFreq(long hash) throws Exception {
      if( hash<LMIN || hash>LMAX ) throw new Exception("hash frequency outside ["+LMIN+" .. "+LMAX+"]");
      return getFreq1(hash);
   }
   static public double getFreq1(long hash) {
      long exponent = (hash & F64_EXPONENT_BIT_MASK) >> 52;
      exponent = (exponent + 929) << 52;
      long freqBits = (hash & F64_BUT_EXPONENT_BIT_MASK) | exponent;
      double freq = Double.longBitsToDouble(freqBits);
      return freq;
   }
   
   /** Add FREQ range
    * @param freqMin - included in the range 
    * @param freqMax - included in the range
    */
   public void add(double freqMin, double freqMax) throws Exception {
      add( MAXORD_F, getHash(freqMin), getHash(freqMax));
   }
   
   /** True if the freq value is in FMOC */
   public  boolean contains(double freq) throws Exception {
      return range.contains( getHash(freq) );
   }
   
   /** Return minimal freq - -1 if empty*/
   public double getFreqMin() {
      if( isEmpty() ) return -1;
      return getFreq1( range.begins(0) );
   }
   
   /** Return maximal freq  - -1 if empty*/
   public double getFreqMax() {
      if( isEmpty() ) return -1;
      return getFreq1( range.ends( range.nranges()-1 ) );
   }
   
   /** Return Frequency for a couple order/npix */
   public static double getFreq(int order, long val) throws Exception {
      int shift = (MAXORD_F - order);
      long t = val<<shift;
      return getFreq(t);
   }
   
//   /** Return the duration of a cell for a specifical order (in microsec) */
//   public static long getDuration(int order) {
//      int shift =(FACT_F/2)*(MAXORD_F - order);
//      return 1L<<shift;
//   }
   
   /** Returns a rangeIterator, which iterates over all individual range
    * @param freqStart
    * @param freqStop
    * @return iterator of ranges
    */
   public Iterator<long[]> jdIterator(double freqStart, double freqStop) throws Exception {
      if( freqStart>freqStop ) throw new InputMismatchException();
      return new JDIterator( getHash(freqStart),getHash(freqStop));
   }
   
   class JDIterator implements Iterator<long[]>{
      int pos, endpos;
      
      JDIterator(long start, long end) {
         pos = range.indexOf(start)/2;;
         if( pos<0 ) pos=0;
         endpos = range.indexOf(end)/2+1;
      }
      
      public boolean hasNext() { return range.sz>0 && pos<endpos; }

      public long [] next() {
         if( !hasNext() ) throw new NoSuchElementException();
         long ret [] = new long[2];
         ret[0] = range.begins(pos);
         ret[1] = range.ends(pos);
         pos++;
         return ret;
      }

      public void remove() { }
   }
   
   /***************************************************** Operations ******************************************/
   
   public boolean isIncluding(Moc moc) throws Exception { 
//      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
//      else 
         if( !(moc instanceof FMoc ) ) throw new Exception("Uncompatible Moc type for FMoc isIncluding test");
      flush(); 
      return range.contains( ((FMoc)moc).range ); 
   }   
   
   public boolean isIntersecting(Moc moc) throws Exception { 
//      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
//      else 
         if( !(moc instanceof FMoc ) ) throw new Exception("Uncompatible Moc type for FMoc isIntersecting test");
      flush(); 
      return range.overlaps( ((FMoc)moc).range ); 
   }
   
   /** Return the Union with another Moc */
   public FMoc union(Moc moc) throws Exception {
//      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
//      else 
         if( !(moc instanceof FMoc ) ) throw new Exception("Uncompatible Moc type for FMoc union");
      return (FMoc) super.union(moc);
   }

   /** Return the Intersection with another Moc */
   public FMoc intersection(Moc moc) throws Exception {
//      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
//      else 
         if( !(moc instanceof FMoc ) ) throw new Exception("Uncompatible Moc type for FMoc subtraction");
      return (FMoc) super.subtraction(moc);
   }
   
   /** Return the subtraction with another Moc */
   public FMoc subtraction(Moc moc) throws Exception {
//      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
//      else 
         if( !(moc instanceof FMoc ) ) throw new Exception("Uncompatible Moc type for FMoc subtraction");
      return (FMoc) super.subtraction(moc);
   }
   
   /** Return the complement */
   public FMoc complement() throws Exception { return (FMoc) super.complement(); }
   
   
  /*************************************************************** I/O *****************************************************/
   
   /** 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 mocOrder = -1;
      try { mocOrder = header.getIntFromHeader("MOCORD_F"); }
      catch( Exception e) {}
      if( mocOrder==-1 ) throw new Exception("Missing MOC order in FITS header (MOCORD_F)");
      readSpecificDataRange(in, naxis1, naxis2, nbyte);
      setMocOrder( mocOrder );
   }
   
   /** Write specifical TMoc properties */
   protected int writeSpecificFitsProp(OutputStream out) throws Exception {
      int n=0;
      out.write( getFitsLine("MOCDIM","FREQUENCY","Physical dimension") );                           n+=80;      
      out.write( getFitsLine("ORDERING","RANGE","Coding method") );                                  n+=80;      
      out.write( getFitsLine("MOCORD_F",""+getMocOrder(),"Frequence MOC resolution (best order)") ); n+=80;      
//      out.write( getFitsLine("FREQSYS",getFreqSys(),"Frequency ref system") );                       n+=80;
      return n;
   }

}
