// 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 TMoc class implements the methods specific to temporal MOCs.
 * It is based on the JD discretization of the time at the µs.
 * See:  IVOA MOC 2.0 standard => https://www.ivoa.net/documents/MOC/
 * @author Pierre Fernique [CDS]
 * @version 1.0 - April 2021 - creation
 *
 * Time Moc resolution table:
 *    0:73067y 276d  16:1y 41d       32:8m 56s       48:8ms 192µs  
 *    1:36533y 320d  17:203d 14h     33:4m 28s       49:4ms 96µs   
 *    2:18266y 342d  18:101d 19h     34:2m 14s       50:2ms 48µs   
 *    3:9133y 171d   19:50d 21h      35:1m 7s        51:1ms 24µs   
 *    4:4566y 268d   20:25d 10h      36:33s 554ms    52:512µs      
 *    5:2283y 134d   21:12d 17h      37:16s 777ms    53:256µs      
 *    6:1141y 249d   22:6d 8h        38:8s 388ms     54:128µs      
 *    7:570y 307d    23:3d 4h        39:4s 194ms     55:64µs       
 *    8:285y 153d    24:1d 14h       40:2s 97ms      56:32µs       
 *    9:142y 259d    25:19h 5m       41:1s 48ms      57:16µs       
 *   10:71y 129d     26:9h 32m       42:524ms 288µs  58:8µs        
 *   11:35y 247d     27:4h 46m       43:262ms 144µs  59:4µs        
 *   12:17y 306d     28:2h 23m       44:131ms 72µs   60:2µs        
 *   13:8y 335d      29:1h 11m       45:65ms 536µs   61:1µs        
 *   14:4y 167d      30:35m 47s      46:32ms 768µs 
 *   15:2y 83d       31:17m 53s      47:16ms 384µs 
 *
 */
public class TMoc extends Moc1D {
   
   static public final double DAYMICROSEC = 86400000000.;

   static final public int  MAXORD_T = 61;      // Max order (<=> resolution at 1 µs)
   static final public int  FACT_T   = 2;       // Factor between two consecutive order
   static final public char DIM_T    = 't';     // Char signature for TMoc
   static final public long NBVAL_T  = pow2( MAXORD_T );   //  // nb of cells at the deepest order
   static final public String SYS_T = "TCB";

   /** Return the deepest possible order (ex: 29 for SMoc, 61 for TMoc) */
   public final int  maxOrder()    { return MAXORD_T; }
   
   /** Return the number of bit shifting between two consecutive orders (ex: 2 for SMoc, 1 for TMoc) */
   public final int  shiftOrder()  { return FACT_T/2; }
   
   /** Return the Moc signature character (ex: 's' for SMoc, 't' for TMOC) */
   public final char cDim()   { return DIM_T; }
   
   /** Return the number of values at the deepest order (ex: 2x2^29x2^29 for SMoc, 2^61 for TMoc) */
   public final long maxVal() { return NBVAL_T; }
   
   /** Return the default reference system */
   public final String sys() { return SYS_T; }


   public TMoc() { super(); }
   public TMoc( int mocOrder ) { super( mocOrder ); }
   public TMoc( String s ) throws Exception { super(s); }
   public TMoc( TMoc moc ) throws Exception { super( moc ); }
   public TMoc( InputStream in ) throws Exception { super(in); }
      
   /** Clone Moc (deep copy) */
   public TMoc clone() throws CloneNotSupportedException {
      TMoc 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 TMoc) ) 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 TMoc dup() {
      TMoc moc = new TMoc();
      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 TMoc) ) throw new Exception("Uncompatible Moc for adding");
      super.add(moc);
   }
   
   /** Get the Timesys */
   public String getTimeSys() { return SYS_T; }

   /************************************ TMoc specifical methods => JD discritization *****************************************/
   
   
   /** Compute the microsec from JD=0 from a date jd (in JD unit=day decimal) and an offset (in JD unit=day) */
   static public long getMicrosec(double jd, long offset) {
      long micron = (long)(jd*DAYMICROSEC);
      return micron + (offset*86400000000L);
   }
   
   /** Add JD range
    * @param jdmin start time (in JD - unit=day) - included in the range (NaN for infinity)
    * @param jdmax end time (in JD - unit=day) - included in the range (NaN for infinity)
    */
   public void add(double jdmin, double jdmax) throws Exception {
      long min = Double.isNaN(jdmin) ? 0L : (long)(jdmin*DAYMICROSEC);
      long max = Double.isNaN(jdmax) ? NBVAL_T : (long)(jdmax*DAYMICROSEC);
      add( MAXORD_T, min,max);
   }
   /** True if the jd date is in TMOC */
   public  boolean contains(double jd) {
      long val = (long)( jd * DAYMICROSEC );
      return range.contains(val);
   }
   
   /** Return minimal time in JD - -1 if empty*/
   public double getTimeMin() {
      if( isEmpty() ) return -1;
      return range.begins(0) / DAYMICROSEC;
   }
   
   /** Return maximal time in JD - -1 if empty*/
   public double getTimeMax() {
      if( isEmpty() ) return -1;
      return range.ends( range.nranges()-1 ) / DAYMICROSEC;
   }
   
   /** Return JD time for a couple order/npix */
   public static double getTime(int order, long val) {
      int shift = /* (FACT_T/2)**/ (MAXORD_T - order);
      long t = val<<shift;
      return t/DAYMICROSEC;
   }
   
   /** Return the duration of a cell for a specifical order (in microsec) */
   public static long getDuration(int order) {
      int shift =(FACT_T/2)*(MAXORD_T - order);
      return 1L<<shift;
   }
   
   /** Returns a rangeIterator, which iterates over all individual range
    * @param jdStart JD start time
    * @param jdStop JD end time
    * @return iterator of range in microseconds
    */
   public Iterator<long[]> jdIterator(double jdStart, double jdStop) {
      if( jdStart>jdStop ) throw new InputMismatchException();
      return new JDIterator((long)(jdStart*DAYMICROSEC),(long)(jdStop*DAYMICROSEC));
   }
   
   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 TMoc ) ) throw new Exception("Uncompatible Moc type for TMoc isIncluding test");
      flush(); 
      return range.contains( ((TMoc)moc).range ); 
   }   
   
   public boolean isIntersecting(Moc moc) throws Exception { 
      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
      else if( !(moc instanceof TMoc ) ) throw new Exception("Uncompatible Moc type for TMoc isIntersecting test");
      flush(); 
      return range.overlaps( ((TMoc)moc).range ); 
   }
   
   /** Return the Union with another Moc */
   public TMoc union(Moc moc) throws Exception {
      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
      else if( !(moc instanceof TMoc ) ) throw new Exception("Uncompatible Moc type for TMoc union");
      return (TMoc) super.union(moc);
   }

   /** Return the Intersection with another Moc */
   public TMoc intersection(Moc moc) throws Exception {
      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
      else if( !(moc instanceof TMoc ) ) throw new Exception("Uncompatible Moc type for TMoc subtraction");
      return (TMoc) super.subtraction(moc);
   }
   
   /** Return the subtraction with another Moc */
   public TMoc subtraction(Moc moc) throws Exception {
      if( moc instanceof STMoc ) moc = ((STMoc)moc).getTimeMoc();
      else if( !(moc instanceof TMoc ) ) throw new Exception("Uncompatible Moc type for TMoc subtraction");
      return (TMoc) super.subtraction(moc);
   }
   
   /** Return the complement */
   public TMoc complement() throws Exception { return (TMoc) 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 {
      boolean protoTMoc=false;
      int mocOrder = -1;
      try { mocOrder = header.getIntFromHeader("MOCORD_T"); }
      catch( Exception e) {}
      
      // Compatibilite avec TMOC protos
      if( mocOrder==-1 ) {
         try { mocOrder = header.getIntFromHeader("MOCORDER")*2+3; protoTMoc=true; } 
         catch( Exception e) {}
      }
      
      String type = header.getStringFromHeader("ORDERING");

      // TMOC 2.0
      if( type!=null && type.equals("RANGE") ) readSpecificDataRange(in, naxis1, naxis2, nbyte);

      // Compatibility with TMOC protos
      else {
         byte [] buf = new byte[naxis1*naxis2];
         readFully(in,buf);
         createMocByUniqUnsorted((naxis1*naxis2)/nbyte,nbyte,buf,protoTMoc);
      }
      
      if( mocOrder==-1 ) throw new Exception("Missing MOC order in FITS header (MOCORD_T)");
      setMocOrder( mocOrder );
   }
   
   /** Write specifical TMoc properties */
   protected int writeSpecificFitsProp(OutputStream out) throws Exception {
      int n=0;
      out.write( getFitsLine("MOCDIM","TIME","Physical dimension") );                            n+=80;      
      out.write( getFitsLine("ORDERING","RANGE","Coding method") );                              n+=80;      
      out.write( getFitsLine("MOCORD_T",""+getMocOrder(),"Time MOC resolution (best order)") );  n+=80;      
      out.write( getFitsLine("TIMESYS",getTimeSys(),"Time ref system") );                             n+=80;
      return n;
   }

}
