/*
 * $Id: Level2VolumeScan.java,v 1.4 2004/10/29 00:14:11 caron 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, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */


package ucar.nc2.iosp.nexrad2;

import ucar.unidata.io.RandomAccessFile;
import ucar.nc2.util.CancelTask;
import ucar.nc2.util.TableParser;

import java.io.*;
import java.util.*;

import org.apache.tools.bzip2.CBZip2InputStream;


/**
 * This class reads a NEXRAD level II data file.
 * It can handle NCDC archives, as well as CRAFT/IDD compressed files.
 *
 * Adapted with permission from the Java Iras software developed
 * by David Priegnitz at NSSL.<p>
 *
 * Documentation on Archive Level II data format can be found at:
 * <a href="http://www.ncdc.noaa.gov/oa/radar/leveliidoc.html">
 * http://www.ncdc.noaa.gov/oa/radar/leveliidoc.html</a>
 *
 * @author caron
 * @author MetApps Development Team
 * @version $Revision: 1.4 $ $Date: 2004/10/29 00:14:11 $
 */
public class Level2VolumeScan {

  // data formats
  public static final String ARCHIVE2 = "ARCHIVE2";
  public static final String AR2V0001 = "AR2V0001";

  ////////////////////////////////////////////////////////////////////////////////////

  // Data file
  RandomAccessFile raf;

  private String dataFormat = null; // ARCHIVE2 or AR2V0001
  private String volumeNo = null;  // volume number (1 to 999)
  private int title_julianDay; // days since 1/1/70
  private int title_msecs; // milliseconds since midnight
  private String stationId; // 4 letter station assigned by ICAO
  private Station station; // from lookup table, may be null
  private Level2Record first, last;

  private int vcp = 0; // Volume coverage pattern


  // List of List of Level2Record
  private ArrayList reflectivityGroups, dopplerGroups;

  private boolean debug = false, debugCompress = false, showMessages = false, showData = false, check = true;

  Level2VolumeScan(RandomAccessFile orgRaf, CancelTask cancelTask) throws IOException {
    this.raf = orgRaf;

    if (debug) System.out.println("Level2VolumeScan on "+raf.getLocation());
    raf.seek(0);
    raf.order(RandomAccessFile.BIG_ENDIAN);

    // volume scan header
    dataFormat = raf.readString(8);
    raf.skipBytes(1);
    volumeNo = raf.readString(3);
    title_julianDay = raf.readInt(); // since 1/1/70
    title_msecs = raf.readInt();
    stationId = raf.readString(4).trim(); // only in AR2V0001
    if (debug) System.out.println(" dataFormat= "+dataFormat+" stationId= "+stationId);

    if (stationId.length() == 0) {
      // try to get it from the filename

      stationId = null;
    }

    // try to find the station
    if (stationId != null) {
      if (stationTableHash == null)
        readStationTable();
      if (stationTableHash != null)
        station = (Station) stationTableHash.get( stationId);
    }

    //see if we have to decompress
    if (dataFormat.equals(AR2V0001)) {
      raf.skipBytes(4);
      String BZ = raf.readString(2);
      if (BZ.equals("BZ")) {
        RandomAccessFile uraf = uncompress( raf, raf.getLocation()+".tmp", true);
        uraf.flush();
        raf.close();
        raf = uraf;
        raf.order(RandomAccessFile.BIG_ENDIAN);
      }

      raf.seek(Level2Record.FILE_HEADER_SIZE);
    }

    ArrayList reflectivity = new ArrayList();
    ArrayList doppler = new ArrayList();

    int recno = 0;
    while (true) {

      Level2Record r = Level2Record.factory(raf, recno++);
      if (r == null) break;

      // skip messages
      if (r.message_type != 1) {
        if (showMessages) r.dumpMessage(System.out);
        continue;
      }

      if (showData) r.dump(System.out);
      if (check) {
        if (!r.checkOk(System.out))
          r.dump(System.out);
      }

      // skip bad
      if (!r.checkOk(null))
        continue;

      // some global params
      if (vcp == 0) vcp = r.vcp;
      if (first == null) first = r;
      last = r;

      if (r.hasReflectData)
        reflectivity.add(r);
      if (r.hasDopplerData)
        doppler.add( r);

      if ((cancelTask != null) && cancelTask.isCancel()) return;
    }
    if (debug) System.out.println(" reflect ok= "+reflectivity.size());
    if (debug) System.out.println(" doppler ok= "+doppler.size());

    reflectivityGroups = sortScans( "reflect", reflectivity);
    dopplerGroups = sortScans( "doppler", doppler);
  }

  private ArrayList sortScans( String name, List scans) {

    // now group by elevation_num, cut
    HashMap groupHash = new HashMap(100);
    for (int i = 0; i < scans.size(); i++) {
      Level2Record record = (Level2Record) scans.get(i);
      Integer hashcode = new Integer(record.hashCode());

      ArrayList group = (ArrayList) groupHash.get( hashcode);
      if (null == group) {
        group = new ArrayList();
        groupHash.put( hashcode, group);
      }

      group.add( record);
    }


    // whattawegot ?
    ArrayList groups = new ArrayList(groupHash.values());
    Collections.sort( groups, new GroupComparator());

    for (int i = 0; i < groups.size(); i++) {
      ArrayList group =  (ArrayList) groups.get(i);
      Level2Record record = (Level2Record) group.get(0);
      if (debug) System.out.println(name+" group "+record+" size= "+group.size());
      if (check) test(group);
    }

    if (check) {
      //test2(groups);
    }

    return groups;
  }

  // do we have same characteristics for all records in a group?
  private int MAX_RADIAL = 368;
  private void test (ArrayList group) {
    Level2Record first = (Level2Record) group.get(0);
    boolean ok = true;
    double sum = 0.0;
    double sum2 = 0.0;
    int[] radial = new int[MAX_RADIAL];

    for (int i = 0; i < group.size(); i++) {
      Level2Record r =  (Level2Record) group.get(i);
      /*
      if (r.getGateCount() != first.getGateCount()) {
        System.out.println(" different number of gates ");
        ok = false;
      }
      if (r.getGateSize() != first.getGateSize()) {
        System.out.println(" different gate size ");
        ok = false;
      }
      if (r.getGateStart() != first.getGateStart()) {
        System.out.println(" different gate start ");
        ok = false;
      } */

      if ((r.radial_num < 0 ) || (r.radial_num >= MAX_RADIAL)) {
        System.out.println(" radial out of range= "+r.radial_num);
        ok = false;
      }
      if (radial[r.radial_num] > 0) {
        System.out.println(" duplicate radial = "+r.radial_num);
        ok = false;
      }
      radial[r.radial_num] = r.recno+1;

      sum += r.getElevation();
      sum2 += r.getElevation() * r.getElevation();
    }
  }

  /* do we have same characteristics for all groups in a variable?
  private void test2 (ArrayList groups) {
    List firstGroup = (List) groups.get(0);
    Level2Record firstRecord = (Level2Record) firstGroup.get(0);
    System.out.println("Group "+firstRecord.getDatatypeName() +" ngates = "+firstRecord.getGateCount() +
        " start = "+firstRecord.getGateStart() +" size = "+firstRecord.getGateSize());

    for (int i = 0; i < groups.size(); i++) {
      List group = (List) groups.get(i);
      Level2Record record = (Level2Record) group.get(0);

      if (record.getGateCount() != firstRecord.getGateCount())
        System.out.println(" diff gates counts = "+record.getGateCount()+" "+firstRecord.getGateCount()+
            " elev= "+record.elevation_num+" "+record.getElevation());
      else
        System.out.println(" ok gates counts elev= "+record.elevation_num+" "+record.getElevation());

      if (record.getGateSize() != firstRecord.getGateSize())
        System.out.println(" diff gates size = "+record.getGateSize()+" "+firstRecord.getGateSize()+
            " elev= "+record.elevation_num+" "+record.getElevation());
      else
        System.out.println(" ok gates size elev= "+record.elevation_num+" "+record.getElevation());

      if (record.getGateStart() != firstRecord.getGateStart())
        System.out.println(" diff gates start = "+record.getGateStart()+" "+firstRecord.getGateStart()+
            " elev= "+record.elevation_num+" "+record.getElevation());
      else
        System.out.println(" ok gates start elev= "+record.elevation_num+" "+record.getElevation());

    }

  } */

  /**
   * Get Reflectivity Groups
   * Groups are all the records for a variable and elevation_num;
   * @return List of type List of type Level2Record
   */
  public List getReflectivityGroups() {
    return reflectivityGroups;
  }

  /**
   * Get Velocity Groups
   * Groups are all the records for a variable and elevation_num;
   * @return List of type List of type Level2Record
   */
  public List getVelocityGroups() {
    return dopplerGroups;
  }

  private class GroupComparator implements Comparator {

    public int compare(Object o1, Object o2) {
      List group1 = (List) o1;
      List group2 = (List) o2;
      Level2Record record1 = (Level2Record) group1.get(0);
      Level2Record record2 = (Level2Record) group2.get(0);

      //if (record1.elevation_num != record2.elevation_num)
        return record1.elevation_num - record2.elevation_num;
      //return record1.cut - record2.cut;
    }
  }

  /**
   * Get data format (ARCHIVE2, AR2V0001) for this file.
   */
  public String getDataFormat() { return dataFormat; }

  /**
   * Get the starting Julian day for this volume
   * @return days since 1/1/70.
   */
  public int getTitleJulianDays() { return title_julianDay; }

  /**
   * Get the starting time in seconds since midnight.
   * @return Generation time of data in milliseconds of day past  midnight (UTC).
   */
  public int getTitleMsecs() { return title_msecs; }

  /**
   * Get the Volume Coverage Pattern number for this data.
   * @return VCP
   * @see Level2Record#getVolumeCoveragePatternName
   */
  public int getVCP() { return vcp; }

  /**
   * Get the 4-char station ID for this data
   * @return station ID (may be null)
   */

  public String getStationId() { return stationId; }

  public String getStationName() { return station == null ? "unknown" : station.name; }
  public double getStationLatitude(){ return station == null ? 0.0 : station.lat; }
  public double getStationLongitude(){ return station == null ? 0.0 : station.lon; }
  public double getStationElevation(){ return station == null ? 0.0 : station.elev; }

  public Date getStartDate() { return first.getDate(); }
  public Date getEndDate() { return last.getDate(); }

  /**
   * Write equivilent uncompressed version of the file.
   * @param raf2  file to uncompress
   * @param ufilename write to this file
   * @param force ignore any existing file
   * @return raf of uncompressed file
   * @throws IOException
   */
  private RandomAccessFile uncompress( RandomAccessFile raf2, String ufilename, boolean force) throws IOException {

    if (!force) {
      File uncompressedFile = new File(ufilename);
      if (uncompressedFile.exists()) {
        // already made it
        return new RandomAccessFile(ufilename, "r");
      }
    }

    // gotta make it
    RandomAccessFile dout2 = new RandomAccessFile(ufilename, "rw");
    raf2.seek(0);
    byte[] header = new byte[Level2Record.FILE_HEADER_SIZE];
    raf2.read(header);
    dout2.write(header);

    boolean eof = false;
    int numCompBytes = 0;
    byte[] ubuff = new byte[40000];
    try {
      while (!eof && (numCompBytes = raf2.readInt()) != -1) {
        if (debugCompress) System.out.println("reading compressed bytes " + numCompBytes+"; output starts at " + dout2.getFilePointer());
        /*
        * For some stupid reason, the last block seems to
        * have the number of bytes negated.  So, we just
        * assume that any negative number (other than -1)
        * is the last block and go on our merry little way.
        */
        if (numCompBytes < 0) {
           //System.err.println("last block?");
           numCompBytes = -numCompBytes;
          eof = true;
        }
        byte[] buf = new byte[numCompBytes];
        raf2.readFully(buf);
        ByteArrayInputStream bis = new ByteArrayInputStream(buf, 2, numCompBytes - 2);

        CBZip2InputStream cbzip2 = new CBZip2InputStream(bis);
        int total = 0;
        int nread = 0;
        while ((nread = cbzip2.read(ubuff)) != -1) {
          dout2.write(ubuff, 0, nread);
          total += nread;
        }
        float nrecords = (float) (total/2432.0);
        if (debugCompress) System.out.println("   unpacked "+total+" num bytes "+nrecords+" records; ouput ends at " + dout2.getFilePointer());
      }
    } catch (EOFException e) {
      e.printStackTrace();
      return dout2;
    }

    return dout2;
  }

  private static boolean showStations = false;
  private static HashMap stationTableHash = null;
  private static void readStationTable() throws IOException {
    stationTableHash = new HashMap();

    ClassLoader cl = Level2VolumeScan.class.getClassLoader();
    InputStream is = cl.getResourceAsStream("resources/tables/nexrad.tbl");

    List recs = TableParser.readTable(is, "3,15,46, 54,60d,67d,73d", 50000);
    for (int i = 0; i < recs.size(); i++) {
      TableParser.Record record = (TableParser.Record) recs.get(i);
      Station s = new Station();
      s.id = "K"+(String) record.get(0);
      s.name = (String) record.get(2) + " " + (String) record.get(3);
      s.lat = ((Double) record.get(4)).doubleValue() * .01;
      s.lon = ((Double) record.get(5)).doubleValue()* .01;
      s.elev = ((Double) record.get(6)).doubleValue();

      stationTableHash.put(s.id, s);
      if (showStations) System.out.println(" station= "+s);
    }
  }

  private static class Station {
    String id, name;
    double lat;
    double lon;
    double elev;

    public String toString() { return id +" <"+ name +">   "+ lat +" "+ lon +" "+ elev; }
  }


// debugging
  static void bdiff(String filename ) throws IOException {

    InputStream in1 = new FileInputStream(filename+".tmp");
    InputStream in2 = new FileInputStream(filename+".tmp2");

    int count = 0;
    int bad = 0;
    while (true) {
      int b1 = in1.read();
      int b2 = in2.read();
      if (b1 < 0) break;
      if (b2 < 0) break;

      if (b1 != b2) {
        System.out.println(count+" in1="+b1+" in2= "+b2);
        bad++;
        if (bad > 130) break;
      }
      count++;
    }
    System.out.println("total read = "+count);
  }


  /** test */
  public static void main(String[] args) throws IOException {
    readStationTable();

    //String filename = "c:/data/radar/level2/6500KHGX20000610_000110.raw";
    //String filename = "c:/data/radar/craft/KPUX_20041020_1334";
    //RandomAccessFile raf = new RandomAccessFile(filename, "r");

    /* raf.order(RandomAccessFile.BIG_ENDIAN);
    RandomAccessFile uraf = uncompress( raf, filename+".tmp", true);
    uraf.close();
    raf.close(); // */

    //bdiff( filename);
    //Level2VolumeScan data = new Level2VolumeScan(raf, null);
  }
}
