// $Id: Nidsiosp.java,v 1.3 2004/12/08 21:38:23 yuanho Exp $
/*
 * Copyright 1997-2004 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, strlenwrite to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ucar.nc2.iosp.nids;

import ucar.ma2.*;
import ucar.nc2.*;

import java.io.*;
import java.awt.image.*;
import java.util.*;
import java.util.zip.DataFormatException;
import java.util.zip.InflaterInputStream;
import java.util.zip.Inflater;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;

/**
 * IOServiceProvider implementation abstract base class to read/write "version 3" netcdf files.
 *  AKA "file format version 1" files.
 *
 *  see   concrete class
 */

public class Nidsiosp implements ucar.nc2.IOServiceProvider {

    protected boolean readonly;
    private static ucar.nc2.NetcdfFile ncfile;
    private static ucar.unidata.io.RandomAccessFile myRaf;
    private static Nidsheader.Vinfo myInfo;
    protected static Nidsheader headerParser;

    final static int Z_DEFLATED = 8;
    final static int DEF_WBITS =  15;

    // used for writing
    protected int fileUsed = 0; // how much of the file is written to ?
    protected int recStart = 0; // where the record data starts

    protected boolean debug = false, debugSize = false, debugSPIO = false;
    protected boolean showHeaderBytes = false;

    public void setProperty( String name, String value) { }
    public void writeData(ucar.nc2.Variable v2, List sectionList, Array values) throws java.io.IOException, InvalidRangeException { }
    public void create(String filename, ucar.nc2.NetcdfFile ncfile, boolean fill) throws java.io.IOException { }
    public ucar.ma2.Array readNestedData(ucar.nc2.Variable v2, java.util.List section, boolean flatten)
         throws java.io.IOException, ucar.ma2.InvalidRangeException {

        return null;
    }

    public boolean isValidFile( ucar.unidata.io.RandomAccessFile raf )
    {
        Nidsheader localHeader = new Nidsheader();
        return( localHeader.isValidFile( raf ));
    }

  /////////////////////////////////////////////////////////////////////////////
  // reading

    public void open(ucar.unidata.io.RandomAccessFile raf, ucar.nc2.NetcdfFile file,
                   ucar.nc2.util.CancelTask cancelTask) throws IOException {

        ncfile = file;
        myRaf = raf;

        headerParser = new Nidsheader();
        headerParser.read(myRaf, ncfile );
        //myInfo = headerParser.getVarInfo();

        ncfile.finish();


    }

    public Array readData(Variable v2, List sectionList) throws IOException, InvalidRangeException  {
    // subset
        Object data = null;
        Array outputData = null;
        byte[] vdata = null;


        Nidsheader.Vinfo vinfo =  (Nidsheader.Vinfo) v2.getSPobject();
        if (vinfo.isZlibed)
            vdata = readCompData(vinfo.hoff, vinfo.doff);
        else
            vdata = readUCompData(vinfo.hoff, vinfo.doff);

        ByteBuffer bos = ByteBuffer.wrap(vdata);


        if ( v2.getName().equals( "azimuth"))
        {
            data = readRadialDataAzi(bos, vinfo);
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }
        else if (v2.getName().equals( "gate"))
        {
            data = readRadialDataGate(bos, vinfo);
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }
        else if (v2.getName().equals( "distance"))
        {
            data = readDistance(vinfo);
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }
        else if (v2.getName().equals( "EchoTop") || v2.getName().equals( "VertLiquid"))
        {
            data = readOneArrayData(bos, vinfo );
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }
        else if (v2.getName().equals( "PrecipArray") )
        {
            data = readOneArrayData1(bos, vinfo );
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }
        else if (v2.getName().equals( "VADWindProfile") )
        {
            data = readWindVectorData(bos, vinfo );
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }
        else
        {
            data = readOneScanData(bos, vinfo );
            outputData = Array.factory( v2.getDataType().getPrimitiveClassType(), v2.getShape(), data);
        }


        return outputData;
  }


    // all the work is here, so can be called recursively
    public Object readOneScanData( ByteBuffer bos, Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      int doff = 0;
      byte[] odata = new byte[ vinfo.xt];
      byte[] pdata = new byte[ vinfo.yt * vinfo.xt];
      byte[] b2 = new byte[2];
      int t = 0;
      bos.position(0);
      for (int radial=0; radial<vinfo.yt; radial++) {
        //bos.get(b2, 0, 2);
        //int test = getInt(b2, 0, 2);
        int runLen = bos.getShort();   //   getInt(vdata, doff, 2 );
        doff += 2;
        if(vinfo.isRadial){
            int radialAngle = bos.getShort();
            doff += 2;
            int radialAngleD = bos.getShort();
            doff += 2;
        }
        byte[] rdata = new byte[runLen*2];

        int tmpp = bos.remaining();
        bos.get(rdata, 0, runLen*2);
        doff += runLen * 2;
        byte[] bdata = readOneBeamData(rdata, runLen, vinfo.xt, doff );


        if ( vinfo.x0 > 0 ) {
            for (int i= 0 ; i<vinfo.x0; i++ ) {
                odata[i] = 0;
            }
        }
        System.arraycopy(bdata, 0, odata, vinfo.x0, bdata.length);

        // copy into odata
        System.arraycopy(odata, 0, pdata, vinfo.xt * radial, vinfo.xt);

      }   //end of for loop

      return pdata;

    }

    public byte[] readOneBeamData(byte[] ddata, int rLen, int xt, int off ) throws IOException, InvalidRangeException  {

         int run = 0;
         byte[] bdata = new byte[xt];

         int nbin = 0;
         int total = 0;
         for ( run = 0; run < rLen*2; run++)
         {
             int drun = convertunsignedByte2Short(ddata[run]) >> 4;
             byte dcode1 = (byte) (convertunsignedByte2Short(ddata[run]) & 0Xf );
             for (int i = 0; i< drun; i++ ) {
                  bdata[nbin++]= dcode1;
                  total++;
             }
         }

         if(total < xt) {
            for ( run = total; run < xt; run++)
            {
                bdata[run]= 0;
            }
         }

         return bdata;
     }
    public Object readWindVectorData( ByteBuffer bos, Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      bos.position(0);
      StructureData[] outdata = new StructureData[vinfo.yt];
      Structure pdata = new Structure(ncfile, null, null,"pdata" );
      short istart = 0;
      short jstart = 0;
      short iend = 0;
      short jend = 0;

        Variable ii0 = new Variable(ncfile, null, pdata, "i0");
        ii0.setDimensions((String)null);
        ii0.setDataType(DataType.SHORT);
        pdata.addMemberVariable(ii0);
        Variable ii1 = new Variable(ncfile, null, pdata, "j0");
        ii1.setDimensions((String)null);
        ii1.setDataType(DataType.SHORT);
        pdata.addMemberVariable(ii1);
        Variable jj0 = new Variable(ncfile, null, pdata, "i1");
        jj0.setDimensions((String)null);
        jj0.setDataType(DataType.SHORT);
        pdata.addMemberVariable(jj0);
        Variable jj1 = new Variable(ncfile, null, pdata, "j1");
        jj1.setDimensions((String)null);
        jj1.setDataType(DataType.SHORT);
        pdata.addMemberVariable(jj1);


      int vlevel = bos.getShort();

      for (int i=0; i< (vinfo.yt); i++) {
           StructureData sdata = new StructureData(pdata);
           Iterator viter = pdata.getVariables().iterator();
           Variable v2 = (Variable) viter.next();
           ArrayObject.D0 sArray = new ArrayObject.D0(Short.class);
           istart  = bos.getShort();
           jstart  = bos.getShort();
           iend = bos.getShort();
           jend = bos.getShort();

           sArray.set(new Short(istart));
           String test = v2.getName();
           sdata.addMember( v2, sArray);

           v2 = (Variable) viter.next();
           test = v2.getName();
           sArray = new ArrayObject.D0(Short.class);
           sArray.set(new Short(jstart));
           sdata.addMember( v2, sArray);

           v2 = (Variable) viter.next();
           test = v2.getName();
           sArray = new ArrayObject.D0(Short.class);
           sArray.set(new Short(iend));
           sdata.addMember( v2, sArray);

           v2 = (Variable) viter.next();
           test = v2.getName();
           sArray = new ArrayObject.D0(Short.class);
           sArray.set(new Short(jend));
           sdata.addMember( v2, sArray);

           outdata[i] = sdata;

      }   //end of for loop

      return outdata;

    }
    // all the work is here, so can be called recursively
    public Object readOneArrayData( ByteBuffer bos, Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      int doff = 0;
      byte[] odata = new byte[ vinfo.xt];
      byte[] pdata = new byte[ vinfo.yt * vinfo.xt];
      byte[] b2 = new byte[2];
      int t = 0;
      bos.position(0);

      for (int radial=0; radial<vinfo.yt; radial++) {

        bos.get(b2);
        int runLen = getUInt(b2, 0, 2); //bos.getShort();   //   getInt(vdata, doff, 2 );
        doff += 2;

        byte[] rdata = new byte[runLen];

        int tmpp = bos.remaining();
        bos.get(rdata, 0, runLen);
        doff += runLen;
        byte[] bdata = readOneRowData(rdata, runLen, vinfo.xt, doff );

        // copy into odata
        System.arraycopy(bdata, 0, pdata, vinfo.xt * radial, vinfo.xt);

      }   //end of for loop

      return pdata;

    }
    public Object readOneArrayData1( ByteBuffer bos, Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      int doff = 0;
      byte[] odata = new byte[ vinfo.xt];
      byte[] pdata = new byte[ vinfo.yt * vinfo.xt];
      byte[] b2 = new byte[2];
      int t = 0;
      bos.position(0);

      for (int row=0; row<vinfo.yt; row++) {

        //bos.get(b2);
        //int runLen = getInt(b2, 0, 2);

        int runLen = bos.getShort();   //   getInt(vdata, doff, 2 );
        doff += 2;

        byte[] rdata = new byte[runLen];

        int tmpp = bos.remaining();
        bos.get(rdata, 0, runLen);
        doff += runLen;
        byte[] bdata = readOneRowData1(rdata, runLen, vinfo.xt, doff );

        // copy into odata
        System.arraycopy(bdata, 0, pdata, vinfo.xt * row, vinfo.xt);

      }   //end of for loop

      return pdata;

    }
    public byte[] readOneRowData1(byte[] ddata, int rLen, int xt, int off ) throws IOException, InvalidRangeException  {
         int doff = off;
         int run = 0;
         byte[] bdata = new byte[xt];

         int nbin = 0;
         int total = 0;
         for ( run = 0; run < rLen/2; run++)
         {
             int drun = convertunsignedByte2Short(ddata[run]);
             run++;
             byte dcode1 = (byte) (convertunsignedByte2Short(ddata[run]) );
             for (int i = 0; i< drun; i++ ) {
                  bdata[nbin++]= dcode1;
                  total++;
             }

         }

        if(total < xt) {
             for ( run = total; run < xt; run++)
             {
                 bdata[run]= 0;
             }
        }

         return bdata;
     }
        public byte[] readOneRowData(byte[] ddata, int rLen, int xt, int off ) throws IOException, InvalidRangeException  {
         int doff = off;
         int run = 0;
         byte[] bdata = new byte[xt];

         int nbin = 0;
         int total = 0;
         for ( run = 0; run < rLen; run++)
         {
             int drun = convertunsignedByte2Short(ddata[run])>>4;
             byte dcode1 = (byte) (convertunsignedByte2Short(ddata[run])& 0Xf );
             for (int i = 0; i< drun; i++ ) {
                  bdata[nbin++]= dcode1;
                  total++;
             }

         }

        if(total < xt) {
             for ( run = total; run < xt; run++)
             {
                 bdata[run]= 0;
             }
        }

         return bdata;
     }
    public Object readRadialDataAzi( ByteBuffer bos, Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      int doff = 0;
      float[] azidata = new float[ vinfo.yt];

      for (int radial=0; radial<vinfo.yt; radial++) {

        int runLen = bos.getShort();   //   getInt(vdata, doff, 2 );
        doff += 2;
        float radialAngle =(float) bos.getShort()/ 10.0f;
        doff += 2;
        int radialAngleD = bos.getShort();
        doff += 2;
        doff += runLen * 2;
        bos.position(doff);
        Float ra = new Float(radialAngle);
        azidata[radial] = ra.floatValue();

      }   //end of for loop

      return azidata;

    }
    public Object readDistance( Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      int doff = 0;
      int[] data = new int[ vinfo.yt * vinfo.xt];

      for (int row=0; row<vinfo.yt; row++) {
          for ( int col = 0; col<vinfo.xt; col++) {
             int i = row * vinfo.yt + col;
             int val = col + vinfo.x0;
             data[i] = val;
          }
      }   //end of for loop

      return data;

    }
    public Object readRadialDataGate( ByteBuffer bos, Nidsheader.Vinfo vinfo ) throws IOException, InvalidRangeException  {
      int doff = 0;
      float[] gatedata = new float[ vinfo.xt];

      float sc = vinfo.y0 * 1.0f;
      for (int rad=0; rad<vinfo.xt; rad++) {
        gatedata[rad] = (vinfo.x0 + rad)* sc;

      }   //end of for loop

      return gatedata;

    }
    // for the compressed data read all out into a array and then parse into requested

   public static byte[] readCompData(long hoff, long doff ) throws IOException {
       int             numin=0;                /* # input bytes processed       */
       long pos = 0;
       long len = myRaf.length();
       myRaf.seek(pos);
       numin = (int)(len - hoff);
       // Read in the contents of the NEXRAD Level III product header

        // nids header process
       byte[] b = new byte[(int)len];
       myRaf.readFully(b);
       /* a new copy of buff with only compressed bytes */

       byte[] comp = new byte[numin - 4];
       System.arraycopy( b, (int)hoff, comp, 0, numin -4 );

       // decompress the bytes
       Inflater inf = new Inflater( false);

       int resultLength = 0;
       int result = 0;
       byte[] inflateData = null;
       byte[] tmp = null;
       long  uncompLen = numin * 2;        /* length of decompress space    */
       byte[] uncomp = new byte[(int)uncompLen];
       inf.setInput(comp, 0, numin-4);

       while ( inf.getRemaining() > 0) {
           try{
             resultLength = inf.inflate(uncomp);
           }
           catch (DataFormatException ex) {
            System.out.println("ERROR on inflation");
            ex.printStackTrace();
          }
           if(resultLength > 0 ) {
               result = result + resultLength;
               inflateData = new byte[result];
               if(tmp != null) {
                  System.arraycopy(tmp, 0, inflateData, 0, tmp.length);
                  System.arraycopy(uncomp, 0, inflateData, tmp.length, resultLength);
               } else {
                  System.arraycopy(uncomp, 0, inflateData, 0, resultLength);
               }
               tmp = new byte[result];
               System.arraycopy(inflateData, 0, tmp, 0, result);
               uncomp = new byte[(int)uncompLen];
           } else {
               int tt = inf.getRemaining();
               inf.reset();
               inf.setInput(comp, numin-4-tt, tt);

           }
        }
        inf.end();

      int off = 0;
      byte   b1, b2;
      b1 = inflateData[0];
      b2 = inflateData[1];
      off  = 2 * (((b1 & 63) << 8) + b2);
      /* eat WMO and PIL */
      for ( int i = 0; i < 2; i++ ) {
        while ( (off < inflateData.length ) && (inflateData[off] != '\n') ) off++;
        off++;
      }

      byte[] data = new byte[ (int)(inflateData.length - off - doff)];

      byte[] hedata = new byte[(int)doff];

      System.arraycopy(inflateData, off, hedata, 0, (int)doff);
      System.arraycopy(inflateData, off+ (int)doff, data, 0, inflateData.length - off -(int)doff);

      return data;

   }
   public static byte[] readUCompData(long hoff, long doff ) throws IOException {
    int  numin=0;
    long pos = 0;
    long len = myRaf.length();
    myRaf.seek(pos);

    numin = (int)(len - hoff );
    // Read in the contents of the NEXRAD Level III product header

    // nids header process
    byte[] b = new byte[(int)len];
    myRaf.readFully(b);
    /* a new copy of buff with only compressed bytes */

    byte[] ucomp = new byte[numin - 4];
    System.arraycopy( b, (int)hoff, ucomp, 0, numin -4 );

    byte[] data = new byte[ (int)(ucomp.length - doff)];

    System.arraycopy(ucomp, (int)doff, data, 0, ucomp.length - (int)doff);

    return data;

   }

   // convert byte array to char array
  static protected char[] convertByteToChar( byte[] byteArray) {
    int size = byteArray.length;
    char[] cbuff = new char[size];
    for (int i=0; i<size; i++)
      cbuff[i] = (char) byteArray[i];
    return cbuff;
  }

   // convert char array to byte array
  static protected byte[] convertCharToByte( char[] from) {
    int size = from.length;
    byte[] to = new byte[size];
    for (int i=0; i<size; i++)
      to[i] = (byte) from[i];
    return to;
  }

  /*
  ** Name:       IsZlibed
  **
  ** Purpose:    Check a two-byte sequence to see if it indicates the start of
  **             a zlib-compressed buffer
  **
  ** Parameters:
  **             buf     - buffer containing at least two bytes
  **
  ** Returns:
  **             SUCCESS 1
  **             FAILURE 0
  **
  */
  int issZlibed( byte[] buf )
  {

      if ( (buf[0] & 0xf) == Z_DEFLATED ) {
        if ( (buf[0] >> 4) + 8 <= DEF_WBITS ) {
          if ( (((buf[0] << 8) + (buf[1])) % 31) == 0 ) {
            return 1;
          }
        }
      }

      return 0;
  }

  int getUInt( byte[] b, int offset, int num )
  {
      int            base=1;
      int            i;
      int            word=0;

      int bv[] = new int[num];

      for (i = 0; i<num; i++ )
      {
        bv[i] = convertunsignedByte2Short(b[offset+i]);
      }

      /*
      ** Calculate the integer value of the byte sequence
      */

      for ( i = num-1; i >= 0; i-- ) {
        word += base * bv[i];
        base *= 256;
      }

      return word;

  }
  int getInt( byte[] b, int offset, int num )
  {
      int            base=1;
      int            i;
      int            word=0;

      int bv[] = new int[num];

      for (i = 0; i<num; i++ )
      {
        bv[i] = convertunsignedByte2Short(b[offset+i]);
      }

      if( bv[0] > 127 )
      {
         bv[0] -= 128;
         base = -1;
      }
      /*
      ** Calculate the integer value of the byte sequence
      */

      for ( i = num-1; i >= 0; i-- ) {
        word += base * bv[i];
        base *= 256;
      }

      return word;

  }

  public short convertunsignedByte2Short(byte b)
  {
     return (short)((b<0)? (short)b + 256 : (short)b);
  }
  protected boolean fill;
  protected HashMap dimHash = new HashMap(50);

  public void flush() throws java.io.IOException {
    myRaf.flush();
  }

  public void close() throws java.io.IOException {
    myRaf.close();
  }

  /** Debug info for this object. */
  public String toStringDebug(Object o) { return null; }


  public static void main(String args[]) throws Exception, IOException, InstantiationException, IllegalAccessException {
    String fileIn = "/home/yuanho/NIDS/N0V_20041117_1646";
    //String fileIn = "c:/data/image/Nids/n0r_20041013_1852";
    ucar.nc2.NetcdfFile.registerIOProvider( ucar.nc2.iosp.nids.Nidsiosp.class);
    ucar.nc2.NetcdfFile ncf = ucar.nc2.NetcdfFile.open(fileIn);

    List alist = ncf.getGlobalAttributes();

    ucar.nc2.Variable v = ncf.findVariable("BaseReflectivity");

    int[] origin  = {0, 0};
    int[] shape = {3000, 4736};

    ArrayByte data = (ArrayByte)v.read(origin,shape);

    ncf.close();


  }


}

/* Change History:
   $Log: Nidsiosp.java,v $
   Revision 1.3  2004/12/08 21:38:23  yuanho
   read gate data

   Revision 1.2  2004/12/07 21:52:06  yuanho
   test nids code

   Revision 1.1  2004/12/07 21:51:41  yuanho
   test nids code

   Revision 1.3  2004/10/15 23:18:44  yuanho
   Nids projection update

   Revision 1.2  2004/10/14 17:14:31  caron
   add Nids reader
   add imageioreader for PNG

   Revision 1.1  2004/10/13 22:58:14  yuanho
   no message

   Revision 1.6  2004/08/17 19:20:04  caron
   2.2 alpha (2)

   Revision 1.5  2004/08/17 00:09:13  caron
   *** empty log message ***

   Revision 1.4  2004/08/16 20:53:45  caron
   2.2 alpha (2)

   Revision 1.3  2004/07/12 23:40:17  caron
   2.2 alpha 1.0 checkin

   Revision 1.2  2004/07/06 19:28:10  caron
   pre-alpha checkin

   Revision 1.1.1.1  2003/12/04 21:05:27  caron
   checkin 2.2

 */