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


/**
 * The Moc2D class implements the methods specific to two-dimensional MOCs (e.g. TIME.SPACE).
 * @author Pierre Fernique [CDS]
 * @version 1.0 - April 2021 - creation
 * @version 1.1 - February 2023 - Support for SFMOC
 */
public abstract class Moc2D extends Moc{
   
   public Range2 range;         // List of ranges (first dimension). Each range pointing to a list of range (second dimension)
   protected Moc1D protoDim1;   // Moc1D concerned for the first dimension (ex: TMoc)
   protected Moc1D protoDim2;   // MOC1D concerned for the second dimension (ex: SMoc)
   
   protected Moc1D cacheDim2Full;  // cache: SMoc union of all space coverages
   
   /** Generic Moc 2D creator */
   protected Moc2D(Moc1D protoDim1, Moc1D protoDim2) { 
      super();
      this.protoDim1 = protoDim1;
      this.protoDim2 = protoDim2;
      clear();
   }
   
   /** Moc2D initialization from 2 individual Mocs
    * @param m1 Moc1D concerning the first dimension (ex: TMoc);
    * @param m2 Moc1D concerning the second dimension (ex: SMoc);
    * @throws Exception
    */
   protected void init( Moc1D m1, Moc1D m2) throws Exception {
      setMocOrder(m1.getMocOrder(), m2.getMocOrder());
      Range rs = m2.seeRangeList();
      for( int i=0; i<m1.range.sz; i+=2 ) {
         range.append(m1.range.r[i], m1.range.r[i+1], rs );
      }
   }
   
   /**
    * Associate to the full first dimension range the Moc m2
    * @param m2
    * @throws Exception
    */
   protected void initDim2( Moc1D m2  ) throws Exception {
      if( !m2.isEmpty() ) {
         Range2 r = new Range2(2);
         r.add( 0L, protoDim1.maxVal(), m2.seeRangeList() );
         setRangeList( r );
      }
   }
   
   /**
    * Associate to the full second dimension range the Moc m1
    * @param m1
    * @throws Exception
    */
   protected void initDim1( Moc1D m1 ) throws Exception {
      if( !m1.isEmpty() ) {
         Range2 r = new Range2( m1.range.sz );
         Range r2 = new Range();
         r2.add( 0L, protoDim2.maxVal() );
         for( int i=0; i<m1.range.sz; i+=2 ) {
            r.append(m1.range.r[i], m1.range.r[i+1], r2 );
         }
         setRangeList( r );
      }
   }
  
   public String toDebug() {
      flush();
      String so1 = ""+getMocOrder1();
      if( protoDim1.mocOrder==-1 ) so1 = "("+so1+")"; 
      char c1= Character.toUpperCase( cDim1() );
      String so2 = ""+getMocOrder2();
      if( protoDim2.mocOrder==-1 ) so2 = "("+so2+")"; 
      char c2= Character.toUpperCase( cDim2() );
      return (c1+"")+(c2+"")
            +"MOC mocOrder="+so1+"/"+so2
//            +" deepestOrder="+getDeepestOrder()
            +" nbRanges="+getNbRanges()
            +" nbCells="+getNbCells()
            +" mem="+getUnitDisk( getMem() )
            +" => "+lowAscii(4);
   }
   
   /** Display the n first ranges at the max order */
  protected String lowAscii(int n) {
      StringBuilder s = new StringBuilder();
      int i;
      n*=2;
      boolean flagCont=false;
      for( i=0; i<range.sz && i<n; i+=2 ) {
         if( s.length()>0 ) s.append(' ');
         String t = flagCont ? "" : "t";
         if( range.r[i+1]-1 == range.r[i] ) s.append(t+range.r[i]);
         else s.append(t+range.r[i]+"-"+(range.r[i+1]-1));
         if( i<range.sz-2 && range.rr[i/2].equals(range.rr[i/2+1])) { flagCont=true; continue; }
         flagCont=false;
         SMoc m = new SMoc(); m.setRangeList(range.rr[i/2] );
         s.append(" s"+m.lowAscii(3) );
      }
      if( i!=range.sz ) s.append("...");
      return s.toString();
   }
   

   /** Return the deepest possible order for the first dimension (ex: 61 for TMoc) */
   public int maxOrder1()   { return protoDim1.maxOrder(); }
   
   /** Return the deepest possible order for the second dimension (ex: 29 for SMoc) */
   public int maxOrder2()   { return protoDim2.maxOrder(); }
   
   /** Return the number of bit shifting between two consecutive orders for the first dimension (ex: 1 for TMoc) */
   public int shiftOrder1() { return protoDim1.shiftOrder(); }
   
   /** Return the number of bit shifting between two consecutive orders for the second dimension (ex: 2 for SMoc) */
   public int shiftOrder2() { return protoDim2.shiftOrder(); }

   /** Return the Moc signature character for the first dimension (ex: 't' for TMoc) */
   public char cDim1() { return protoDim1.cDim(); }
   
   /** Return the Moc signature character for the second dimension (ex: 's' for SMoc */
   public char cDim2() { return protoDim2.cDim(); }
   
   /** Acces to the list of ranges (no copy) */
   public Range seeRangeList() { flush(); return range; }
   
   /** Set the list of ranges - Warning: no copy */
   public void setRangeList( Range range ) { this.range=(Range2)range; }
   
   /** Return the Moc order of the first dimension */
   public int getMocOrder1() { return protoDim1.getMocOrder(); }
   
   /** Return the Moc order of the second dimension */
   public int getMocOrder2() { return protoDim2.getMocOrder(); }
   
   /** Set Moc orders of the first and the second dimension simultaneously (fastest than in two steps) */
   public void setMocOrder( int order1, int order2 ) throws Exception { 
      if( order1<-1 || order1>protoDim1.maxOrder() ) 
         throw new Exception("MocOrder error ("+order1+" not in [0.."+protoDim1.maxOrder()+"])");
      if( order2<-1 || order2>protoDim2.maxOrder() ) 
         throw new Exception("MocOrder error ("+order2+" not in [0.."+protoDim2.maxOrder()+"])");
     
      if( order1!=-1 || order2!=-1 ) {
         // If the Moc order was not yet defined, it was assumed to be at the best resolution
         if( order1!=-1 && protoDim1.mocOrder==-1 ) protoDim1.mocOrder=protoDim1.maxOrder();
         if( order2!=-1 && protoDim2.mocOrder==-1 ) protoDim2.mocOrder=protoDim2.maxOrder();

         // If the new mocOrder is smaller than the previous one, 
         // the cells must be aggregated according to the change in resolution
         if( order1<protoDim1.mocOrder || order2<protoDim2.mocOrder ) {
            int shift1 = order1==-1 ? 0 : protoDim1.maxOrder() - order1;
            int shift2 = order2==-1 ? 0 : protoDim2.maxOrder() - order2;
            range = range.degrade( shift1 * protoDim1.shiftOrder(), shift2 * protoDim2.shiftOrder() ); 
            resetCache();
         }
      }
      protoDim1.mocOrder=order1;
      protoDim2.mocOrder=order2;
   }
   
   /** Set Moc order of the first dimension */
   public void setMocOrder1( int order1 ) throws Exception { setMocOrder( order1, protoDim2.mocOrder ); }
   
   /** Set Moc order of the second dimension */
   public void setMocOrder2( int order2 ) throws Exception {  setMocOrder( protoDim1.mocOrder, order2  ); }
   
   /** Returns the value of the beginning of the interval expressed at the maximum order (first dimension) */
   public long getStart1(int order, long val) { return protoDim1.getStart(order,val); }
   
   /** Returns the value of the end of the interval (excluded) expressed at the maximum order (first dimension)  */
   public long getEnd1(int order, long val) { return protoDim1.getEnd(order,val); }
   
   /** Returns the value of the beginning of the interval expressed at the maximum order (second dimension) */
   public long getStart2(int order, long val) { return protoDim2.getStart(order,val); }
   
   /** Returns the value of the end of the interval (excluded) expressed at the maximum order (second dimension)  */
   public long getEnd2(int order, long val) { return protoDim2.getEnd(order,val); }
   
   public void add(long val1, long val2, Range r) {
      int mocOrder1 = protoDim1.mocOrder;
      if( mocOrder1!=-1 ) {
         int shift = (maxOrder1()-mocOrder1) * shiftOrder1();
         val1 = (val1>>>shift ) << shift;
         val2   = (((val2>>>shift )+1L)   << shift) -1L;
      }
      range.add(val1, val2+1L, r);
      resetCache();
   }
   
   /** Add one cell to the Moc2D.
    * @param order1 Order of the cell (first dimension)
    * @param val1 Value of the cell (first dimension)
    * @param order2 Order of the cell (second dimension)
    * @param val2 Value of the cell (second dimension)
    * @throws Exception
    */
   public void add(int order1, long val1, int order2, long val2) throws Exception  { add(order1,val1,val1, order2, val2,val2); }
   
   /** Add a list of consecutive Moc cells.
    * @param order1 Order of the cells (first dimension)
    * @param firstVal1 First value (first dimension)
    * @param lastVal1 Last value (included) (first dimension)
    * @param order2 Order of the cells (second dimension)
    * @param firstVal2 First value (second dimension)
    * @param lastVal2 Last value (included) (second dimension)
    */
   public void add(int order1,long firstVal1, long lastVal1, int order2,long firstVal2, long lastVal2 ) throws Exception {
      int shift;
      
      // FIRST dimension
      int mocOrder1 = protoDim1.mocOrder;
      
      // Do we have to degrade the order/val (if mocOrder is smaller than the order) ?
      if( mocOrder1!=-1 && mocOrder1<order1 ) {
         shift = (order1-mocOrder1) * shiftOrder1();
         firstVal1 = (firstVal1>>>shift ) << shift;
         lastVal1   = (((lastVal1>>>shift )+1L)   << shift) -1L;
      }
      
      // Values converted to the maxOrder as range
      shift = ( maxOrder1()-order1 ) * shiftOrder1();
      long start1 = firstVal1<<shift;
      long end1 =  (lastVal1+1L)<<shift;
      
      // SECOND dimension
      int mocOrder2 = protoDim2.mocOrder;
      
      // Do we have to degrade the order/val (if mocOrder is smaller than the order) ?
      if( mocOrder2!=-1 && mocOrder2<order2 ) {
         shift = (order2-mocOrder2) * shiftOrder2();
         firstVal2 = (firstVal2>>>shift ) << shift;
         lastVal2   = (((lastVal2>>>shift )+1L)   << shift) -1L;
      }
      
      // Values converted to the maxOrder as range
      shift = ( maxOrder2()-order2 ) * shiftOrder2();
      long start2 = firstVal2<<shift;
      long end2 =  (lastVal2+1L)<<shift;
    
      Range r = new Range();
      r.append(start2,end2);
      
      range.add(start1,end1, r);
      resetCache();
   }
   
   /** Return true in case of equality (only check data - no properties, nor MocOrder) */
   public boolean equals( Object o) {
      if( o==this ) return true;
      if( o==null || !(o instanceof Moc2D) ) return false;
      Moc2D moc = (Moc2D) o;
      if( cDim1()!=moc.cDim1() ) return false;
      if( cDim2()!=moc.cDim2() ) return false;
      return range.equals( ((Moc2D)o).range); 
   }
   
   public int hashCode() { 
      if( cacheHashCode== -1 ) cacheHashCode=range.hashCode(); 
      return cacheHashCode;
   }
   
   /** Deep copy. The source is this, the target is the Moc in parameter */
   protected void clone1( Moc moc ) throws CloneNotSupportedException {
      if( !(moc instanceof Moc2D) ) throw new CloneNotSupportedException("Uncompatible type of MOC for clone. Must derived from Moc2D");
      super.clone1( moc );
      Moc2D m = (Moc2D)moc;
      m.protoDim1 = (Moc1D)protoDim1.clone();
      m.protoDim2 = (Moc1D)protoDim2.clone();
      m.range= (range==null) ? null : new Range2(range);
      
      ((Moc2D)moc).cacheDim2Full=cacheDim2Full==null ? null : (Moc1D) cacheDim2Full.clone();
   }
   
   /** Return true if the Moc is empty (no coverage) */
   public boolean isEmpty() { flush(); return range.sz==0; }
      
   /** Return true if the Moc is full (full coverage) */
   public boolean isFull() {
      flush();
      if( !( range.sz==2 && range.r[0]==0 && range.r[1]==protoDim1.maxVal() )) return false;
      Moc1D m;
      m = (Moc1D)protoDim2.dup();
      m.setRangeList( range.rr[0] );
      return m.isFull();
   }

   /** Return the coverage pourcentage of the Moc */
   public double getCoverage() {
      // NOT YET IMPLEMENTED
      return -1;
   }
   
   /** Recalculates the metrics associated with the MOC hierarchical view: 
    * the number of hierarchical cells, the deepest order used... */
   protected void computeHierarchy() {
      flush(); 
      int nb=0;
      int deep=0;
      for( MocCell mc : this ) {
         nb += mc.moc.getNbCells();
         int d=mc.moc.getDeepestOrder();
         if( d>deep ) deep=d;
      }
      cacheNbCells = nb;
      cacheDeepestOrder=deep;
   }
   
   /** Return the number of ranges - first dimension */
   public int getNbRanges() { flush(); return range.sz/2; }
   
   /** Return approximatively the amount of memory used for storing this MOC in RAM (in bytes) */
   public long getMem() {
      flush(); 
      long mem = range.getMem();
      if( cacheDim2Full!=null ) mem+=cacheDim2Full.getMem();
      return mem; 
   }

   /** Clear the MOC - data only (not the properties, nor the mocOrder) */
   public void clear() { 
      super.clear();
      buf=null;
      moc1=null;
      inDim1=false;
      range = new Range2();
      if( protoDim1!=null ) protoDim1.sys=null;
      if( protoDim2!=null ) protoDim2.sys=null;
      
      cacheDim2Full=null;
   }
   
   protected void resetCache() {
      super.resetCache();
      cacheDim2Full=null;
   }
   
   /** Degrades the resolution(s) of the MOC until the RAM size of the MOC is reduced under the specified maximum (expressed in bytes). */
   public boolean reduction( long maxSize) throws Exception { return reduction( maxSize, null); }
   
   /** Degrades the resolution(s) of the MOC until the RAM size of the MOC is reduced under the specified maximum (expressed in bytes).
    * @param maxMB size max in MB
    * @param priority Indicates the dimensions to be degraded as a priority in the form of a list of the dimension's signature characters
    *                 ex: "t" for time only, "s" space only, "st"-both alternatively, space first (default), "ttts", ...
    */
   public boolean reduction( long maxMB, String priority) throws Exception { 
      flush();
      boolean rep=false;
      if( maxMB<=0 ) throw new Exception("negative or null size not allowed");
      if( priority==null || priority.trim().length()==0 ) { 
         int mocOrder1 = getMocOrder1();
         int mocOrder2 = getMocOrder2();
         if( (double)mocOrder2/protoDim2.maxOrder() > (double)mocOrder1/protoDim1.maxOrder() ) {
            priority=(cDim2()+"")+(cDim1()+""); 
         } else priority=(cDim1()+"")+(cDim2()+""); 
      }
      while( getMem()>maxMB && (getMocOrder1()>0 || getMocOrder2()>0) ) {
         char c = priority.charAt(0);
         if( c==cDim1() && getMocOrder1()>0 ) setMocOrder1( getMocOrder1()-1 );
         else if( c==cDim2() && getMocOrder2()>0 ) setMocOrder2( getMocOrder2()-1 );
         else throw new Exception("Unknown MOC signature ["+c+"]");
         priority = priority.substring(1)+(c+"");
         rep=true;
      }
      return rep;
   }
   
   /** Return true if the moc is compatible for operation (same sys) */
   public boolean isCompatible( Moc moc ) {
      if( !getClass().equals( moc.getClass()) ) return false;
      Moc2D m = (Moc2D)moc;
      return protoDim1.isCompatible(m.protoDim1) && protoDim2.isCompatible(m.protoDim2);
   }
   
   /** Generic operations: 0-union, 1-intersection, 2-subtraction */
   protected Moc2D operation(Moc moc, int op) throws Exception {
      if( !isCompatible(moc) ) throw new Exception("Uncompatible MOC");
      flush();
      moc.flush();
      Moc2D m = (Moc2D)moc;
      Moc2D res = (Moc2D)dup();
      // Faudrait-il prendre en compte un minorder ? => res.setMinOrder(  Math.min(minOrder,m.minOrder) );
      res.setMocOrder1( getMocOrder4op( getMocOrder1(), m.getMocOrder1() ) );
      res.setMocOrder2( getMocOrder4op( getMocOrder2(), m.getMocOrder2() ) );
      
      switch(op) {
         case 0 : res.range = range.union(m.range);        break;
         case 1 : res.range = range.intersection(m.range); break;
         case 2 : res.range = range.difference(m.range);   break;
      }
      res.range.trimSize();
      return res;
   }
   
   /** Write MOC to an output stream in ASCII serialization */
   public void writeASCII(OutputStream out) throws Exception {
      flush();
      if( isEmpty() ) return;
      int order;
      int maxOrder2 = 0;
      int maxOrder1 = 0;
      int nbRanges = getNbRanges();
      boolean flagNL = nbRanges>MAXWORD;
      boolean first=true;

      StringBuilder res= new StringBuilder(1000);
      for( int i=0; i<nbRanges; i++ ) {
         
         // Writing the moc of the first dimension associated with the same Moc of the second dimension
         Range r1 = new Range();
         r1.push(range.r[i*2]);
         r1.push(range.r[i*2+1]);
         while( i<nbRanges-1 && range.rr[i].equals(range.rr[i+1] )) {
            i++;
            r1.push(range.r[i*2]);
            r1.push(range.r[i*2+1]);
         }
         Moc1D mocDim1 = (Moc1D)protoDim1.dup();
         mocDim1.setRangeList( r1 );
         if( !first ) res.append( flagNL ? CR:" ");
         else first=false;
         res.append(cDim1());
         writeASCIIFlush(out,res,false);
         order=writeASCII(out,mocDim1,flagNL, true);
         if( order>maxOrder1 ) maxOrder1=order;
         
         // Writing the Moc of the second dimension associated with the range
         res.append( flagNL ? CR:" ");
         res.append(cDim2());
         writeASCIIFlush(out,res,false);
         Moc1D mocDim2 = (Moc1D)protoDim2.dup();
         mocDim2.setRangeList( range.rr[i] );
         order=writeASCII(out,mocDim2,flagNL, true);
         if( order>maxOrder2 ) maxOrder2=order;
      }

      // Add moc orders at the end of the stream if necessary
      if( maxOrder1!=getMocOrder1() || maxOrder2!=getMocOrder2() ) {
         res.append(flagNL ? CR : " " );
         res.append(cDim1()+""+getMocOrder1()+"/ "+cDim2()+getMocOrder2()+"/");
         if( flagNL ) res.append(CR);
      }

      writeASCIIFlush(out,res,false);
   }
   
   /** Write MOC to an output stream in JSON serialization (non IVOA standard) */
   public void writeJSON(OutputStream out) throws Exception { 
      flush();
      if( isEmpty() ) return;
      int order;
      int maxOrder2 = 0;
      int maxOrder1 = 0;
      int nbRanges = getNbRanges();
      boolean flagNL =  getNbCells()>MAXWORD;
      boolean flagNL1;
      boolean first=true;

      StringBuilder res= new StringBuilder(1000);
      res.append("[");
      res.append(flagNL ? CR : " " );
      
      for( int i=0; i<nbRanges; i++ ) {
         
         // Writing the moc of the first dimension associated with the same Moc of the second dimension
         Range r1 = new Range();
         r1.push(range.r[i*2]);
         r1.push(range.r[i*2+1]);
         while( i<nbRanges-1 && range.rr[i].equals(range.rr[i+1] )) {
            i++;
            r1.push(range.r[i*2]);
            r1.push(range.r[i*2+1]);
         }
         Moc1D mocDim1 = (Moc1D)protoDim1.dup();
         mocDim1.setRangeList( r1 );
         if( !first ) {
            res.append(",");
            res.append( flagNL ? CR:" ");
         } else first=false;
         res.append(" { \""+cDim1()+"\":");
         writeASCIIFlush(out,res,false);
         flagNL1 = !flagNL ? false : mocDim1.getNbCells()>MAXWORD;
         order=mocDim1.writeJSON(out, flagNL1);
         if( order>maxOrder1 ) maxOrder1=order;
         
         // Writing the Moc of the second dimension associated with the range
         res.append(flagNL1?CR:"");
         res.append(flagNL?" , ":", ");
         res.append("\""+cDim2()+"\":");
         writeASCIIFlush(out,res,false);
         Moc1D mocDim2 = (Moc1D)protoDim2.dup();
         mocDim2.setRangeList( range.rr[i] );
         flagNL1 = !flagNL ? false : mocDim2.getNbCells()>MAXWORD;
         order=mocDim2.writeJSON(out, flagNL1);
         if( order>maxOrder2 ) maxOrder2=order;
         res.append(" }");
      }

      // Add moc orders at the end of the stream if necessary
      if( maxOrder1!=getMocOrder1() || maxOrder2!=getMocOrder2() ) {
         res.append(", ");
         res.append(flagNL ? CR : " " );
         res.append(" { \""+cDim1()+"\":{ \""+getMocOrder1()+"\":[] },");
         res.append(flagNL ? CR+"   " : " " );
         res.append("\""+cDim2()+"\":{ \""+getMocOrder2()+"\":[] }");
         res.append(flagNL ? CR+" " : " " );
         res.append("}");
         res.append(flagNL ? CR : " " );
      }

      res.append("]");
      res.append(flagNL ? CR : "" );
      writeASCIIFlush(out,res,false);
   }
   
   /** Return the number of bytes used for coding each FITS value (=> always long) */
   public int sizeOfCoding() { return 8; }

   /** Return the number of values to write in FITS serialization */
   public int getNbCoding() {
      int nb = 0;
      for( int i=0; i<range.sz; i+=2 ) {
         nb += 2L;
         if( i>0 && range.rr[i/2].equals(range.rr[i/2-1])  ) continue;
         nb += range.rr[i/2].sz;
      }
      return nb;
   }
   
   /** Provide an Iterator on the MOC cell List (range highest order architecture for Moc2D)
    * @param flagRange not use for Moc2D
    * @return mocCell => dim,order,startVal,endVal,Moc1D
    */
   public Iterator<MocCell> cellIterator(boolean flagRange) { flush(); return new Dim2Iterator(  ); }

   private class Dim2Iterator implements Iterator<MocCell> {
      int i;
      int order;
      long shift;
      char cdim;

      Dim2Iterator() {
         i=0;
         order = getMocOrder1();
         shift = (maxOrder1() - order) * shiftOrder1();
         cdim = cDim1();
      }

      public boolean hasNext() { return i<range.sz/2; }

      public MocCell next() {
         if( !hasNext() ) return null;
         Moc1D moc = (Moc1D) protoDim2.dup();
         moc.setRangeList( range.rr[i] );
         MocCell cell = new MocCell();
         cell.dim = cdim;
         cell.order = order;
         cell.start = range.r[i*2] >>> shift;
         cell.end   = range.r[i*2+1] >>> shift;
         cell.moc = moc;
         i++;
         return cell;
      }

      public void remove() {  }
   }

   /********************************************************* Binary utilities *******************************************/
   
   static public long MASK_T = 1L<<63;
   static public long UNMASK_T = ~MASK_T;

   protected boolean isCodedTime(long a)  { return (a&MASK_T)!=0L; }
   protected long codeTime(long a)  { return a|MASK_T; }
   protected long decodeTime(long a) { return a&UNMASK_T; }
   
   static public long MASK_F = 3L<<62;
   static public long UNMASK_F = ~MASK_F;

   protected boolean isCodedFreq(long a)  { return (a&MASK_F)!=0L; }
   protected long codeFreq(long a)  { return a|MASK_F; }
   protected long decodeFreq(long a) { return a&UNMASK_F; }

   
   protected abstract boolean isCodedDim1(long a);
   protected abstract long codeDim1( long a);
   protected abstract long decodeDim1( long a);
   
   
   /** Write Moc2D data in Ranges
    * @param out output stream
    * @param mode RAW or COMP_SINGLETON
    * @return number of bytes written
    */
   public int writeSpecificDataRange(OutputStream out, int mode) throws Exception {
      int size = 0;
      Moc1D m1 = (Moc1D)protoDim1.dup();
      Moc1D m2 = (Moc1D)protoDim2.dup();
      Range r1 = new Range(500);
      
      for( int i=0; i<range.sz; i+=2 ) {
         r1.push( codeDim1(range.r[i]) );
         r1.push( codeDim1(range.r[i+1]) );

         // Si le prochain Moc dim2 est identique, on itère
         if( i<range.sz-2 && range.rr[i/2].equals(range.rr[i/2+1]) ) continue;
         
         // On écrit les ranges du TMOC courant
         m1.setRangeList(r1);
         size += m1.writeSpecificDataRange(out, mode);
         r1.sz=0;

         // et les ranges du SMOC associé
         m2.setRangeList(range.rr[i/2]);
         size += m2.writeSpecificDataRange(out, mode);
      }
      return size;
   }

   /** Create Moc2D from the list of fits values */
   public void readSpecificDataRange(int nval,byte [] t,int mode) throws Exception {
      Range m1 = new Range(500);
      Range m2 = new Range(500);
      long u1 = 1L<< (( maxOrder1() - getMocOrder1()) * shiftOrder1());
      long u2 = 1L<< (( maxOrder2() - getMocOrder2()) * shiftOrder2());
      
      for( int k=0; k<=nval; k++ ) {
         
         // the extra val is only used to achieved the process
         long val = k==nval ? 0 : readLong(t,k*8);
         
         // First dimension indice ? Or extra final step ?
         if( k==nval || isCodedDim1(val) ) {
            
            // Assignments of the previous spatial indices to the corresponding time intervals (no copy)
            if( !m2.isEmpty() ) {
               
               // Copie & decompression des ranges temporels et spatiaux
               if( mode==COMPRESS_SINGLETON ) {
                  m1 = uncompressRange(m1.r, m1.sz, u1);
                  m2 = uncompressRange(m2.r, m2.sz, u2);
               } else  m2.trimSize();
               for( int j=0; j<m1.sz; j+=2 ) range.push(m1.r[j], m1.r[j+1], m2);
               
               // We are preparing the ranges for the next lists
               m1.sz=0;
               m2=new Range(500);
            }
            
            // Memorization of the temporal value;
            if( k<nval ) {
               val = decodeDim1(val);
               m1.push( val );
            }

         //  Spatial list
         } else m2.push(val);
      }
    }


   /********************************************************* ASCII & JSON parser *******************************************/
   
   
   public void flush() { 
      if( buf!=null ) try { addToken(null); } catch( Exception e ) {} 
   }
   
   // To build as you go the STMOC via addToken
   private StringBuilder buf=null;   // The reading buffer
   private Moc1D moc1=null;          // The last Moc1D read during parsing
   private boolean inDim1=false;     // Specify the state of the parsing

   protected void addToken(String token) throws Exception {
      
      // End of treatment or new dimension 1?
      if( token==null || token.charAt(0)==cDim1() ) {

         // Check dimension interleaving
         if( token!=null ) {
            if( inDim1 ) throw new Exception("Moc syntax error");
            inDim1 = !inDim1;
         }

         // Insertion of the previous couple (range,moc1D) if existing 
         if( moc1!=null ) {
            // I create the MOC dim2 of the buffer
            Moc1D moc2 = (Moc1D)protoDim2.dup();
            moc2.add( buf.toString() );
            protoDim2.biggestOrder = Math.max( protoDim2.biggestOrder, moc2.biggestOrder );
            
           // I associate this Moc dim2 with the dim1 ranges (no copies)
            for( int i=0; i<moc1.range.sz; i+=2 ) {
               range.add(moc1.range.r[i], moc1.range.r[i+1], moc2.range );
            }
            
            moc1=null;
            buf=null;
         }
         
         // Storage in a buffer of the content up to dimension 2 (without the prefix of the dimension)
         if( token!=null ) buf = new StringBuilder( token.substring(1) );
         
      // New dimension 2 ?
      } else if( token.charAt(0)==cDim2() ) {
         
         // Check dimension interleaving
         if( !inDim1 ) throw new Exception("Moc syntax error");
         inDim1 = !inDim1;
         
         // Moc dim1 generation of buffered content
         moc1 = (Moc1D) protoDim1.dup();
         moc1.biggestOrder = protoDim1.biggestOrder;
         moc1.add( buf.toString() );
         protoDim1.biggestOrder = Math.max( protoDim1.biggestOrder, moc1.biggestOrder );

         // Storage of the content up to the next dimension 1 (without the prefix of the dimension)
         buf = new StringBuilder( token.substring(1) );
         
      //  Storage in a buffer of the current token
      } else {
         if( buf==null ) throw new Exception("Moc syntax error => token ["+token+"]");
         buf.append(' ');
         buf.append(token);
      }
   }
   
}
