// $Id: Aggregation.java,v 1.2 2004/12/01 05:53:42 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.ncml;

import org.jdom.Element;

import ucar.ma2.Array;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;
import ucar.nc2.*;
import ucar.nc2.dataset.*;
import ucar.nc2.util.CancelTask;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.io.IOException;

/**
 * @author john
 */
public class Aggregation {
  NcMLReader reader;
  String dimName;
  boolean isJoinNew = false;
  ArrayList vars = new ArrayList(); // names
  ArrayList nestedDatasets = new ArrayList(); // AggDataset objects
  int ncoords = 0;

  // union
  Aggregation(String type) {
    //this.aggType = type;
  }

  void addDataset( NetcdfDataset ds) {
    Dataset nested = new Dataset( ds);
    nestedDatasets.add( nested);
  }

  // existing
  Aggregation( NcMLReader reader, String dimName, String type) {
    this.reader = reader;
    this.dimName = dimName;
    //this.aggType = type;
    isJoinNew = (type.equalsIgnoreCase("joinNew"));
  }

  void addDataset( String location, String ncoordS, String coordValue, Element netcdfElem, CancelTask cancelTask) throws IOException {
    Dataset nested = new Dataset( location, ncoordS, coordValue, netcdfElem, ncoords, cancelTask);
    nestedDatasets.add( nested);
    ncoords += nested.getNcoords( cancelTask);
  }

  public int getNcoords() { return ncoords; }
  public List getNestedDatasets() { return nestedDatasets; }
  public DataType getCoordinateType() {
    Dataset first = (Dataset) nestedDatasets.get(0);
    return (first.coordValueS == null) ? DataType.DOUBLE : DataType.STRING;
  }

  public void close() throws IOException {
    Iterator iter = nestedDatasets.iterator();
    while (iter.hasNext()) {
      Dataset nested = (Dataset) iter.next( );
      if (nested.ds != null)
        nested.ds.close();
    }
  }

  public Array read(VariableDS mainv, CancelTask cancelTask) throws IOException {

    Array allData = Array.factory( mainv.getDataType().getPrimitiveClassType(), mainv.getShape());
    int destPos = 0;

    Iterator iter = nestedDatasets.iterator();
    while (iter.hasNext()) {
      Dataset vnested = (Dataset) iter.next( );
      Array varData = vnested.read(mainv, cancelTask);
      if ((cancelTask != null) && cancelTask.isCancel())
        return null;

      Array.arraycopy( varData, 0, allData, destPos, (int) varData.getSize());
      destPos += varData.getSize();
    }

    return allData;
  }

  public Array read(VariableDS mainv, CancelTask cancelTask, List section) throws IOException, InvalidRangeException {

    Array sectionData = Array.factory( mainv.getDataType().getPrimitiveClassType(), Range.getShape(section));
    int destPos = 0;

    Range joinRange = (Range) section.get(0);
    List nestedSection = new ArrayList( section); // copy
    List innerSection = section.subList(1, section.size());

    Iterator iter = nestedDatasets.iterator();
    while (iter.hasNext()) {
      Dataset nested = (Dataset) iter.next( );
      Range nestedJoinRange = nested.getNestedJoinRange( joinRange);
      if (nestedJoinRange == null)
        continue;

      Array varData = null;
      if (isJoinNew) {
        varData = nested.read(mainv, cancelTask, innerSection);
      } else {
        nestedSection.set( 0, nestedJoinRange);
        varData = nested.read(mainv, cancelTask, nestedSection);
      }

      if ((cancelTask != null) && cancelTask.isCancel())
        return null;

      Array.arraycopy( varData, 0, sectionData, destPos, (int) varData.getSize());
      destPos += varData.getSize();
    }

    return sectionData;
  }

  public class Dataset {
    String location;
    int ncoord; // n coordinates in outer dimension for this dataset; joinExisting
    String coordValueS; // if string valued coordinate - joinNew
    double coordValue; // if numeric valued coordinate - joinNew
    Element netcdfElem;
    NetcdfDataset ds;
    int aggStart = 0, aggEnd = 0; // index in aggregated dataset; aggStart <= i < aggEnd

    Dataset( NetcdfDataset ds) { this.ds = ds; } // union

    // joinNew or joinExisting
    Dataset( String location, String ncoordS, String coordValueS, Element netcdfElem, int aggStart, CancelTask cancelTask) throws IOException {
      this.location = location;

      if (ncoordS != null)
        this.ncoord = Integer.parseInt(ncoordS);
      else if (isJoinNew)
        this.ncoord = 1;

      if (coordValueS != null) {
        try {
          this.coordValue = Double.parseDouble(coordValueS);
        } catch (NumberFormatException e) {
          this.coordValueS = coordValueS;
        }
      }

      this.netcdfElem = netcdfElem;
      this.aggStart = aggStart;
      this.aggEnd = aggStart + getNcoords(cancelTask);
    }

    // wantStart, wantStop are the indices in the aggregated dataset, wantStart <= i < wantEnd
    // if this overlaps, set the Range required for the nested dataset
    // if no overlap, return null
    // note this should handle strides ok
    Range getNestedJoinRange( Range totalRange) throws InvalidRangeException {
      int wantStart = totalRange.first();
      int wantStop = totalRange.last()+1; // Range has last inclusive, we use last exclusive

      if (!isNeeded( wantStart, wantStop))
        return null;

      int start = Math.max( aggStart, wantStart) - aggStart;
      int stop = Math.min( aggEnd, wantStop) - aggStart;

      return new Range( start, stop-1, totalRange.stride()); // Range has last inclusive
    }

    // wantStart, wantStop are the indices in the aggregated dataset, wantStart <= i < wantEnd
    // find out if this overlaps this nested Dataset indices
    boolean isNeeded( int wantStart, int wantStop) {
      if (wantStart >= wantStop)
        return false;
      if ((wantStart >= aggEnd) || (wantStop <= aggStart))
        return false;
      return true;
    }

    NetcdfDataset openDataset( CancelTask cancelTask) throws IOException {
      if (ds == null) {

        ds = reader.readNetcdf(null, location, netcdfElem,  cancelTask);
        System.out.println(" opened nested dataset "+location);
      }
      return ds;
    }

    int getNcoords(CancelTask cancelTask) throws IOException {
      if (ncoord <= 0) {
        openDataset( cancelTask);
        Dimension d = ds.getRootGroup().findDimension( dimName);
        if (d != null)
          ncoord = d.getLength();
      }
      return ncoord;
    }

    public Array read(VariableDS mainv, CancelTask cancelTask) throws IOException {
      openDataset( cancelTask);
      if ((cancelTask != null) && cancelTask.isCancel())
        return null;

      Variable v = ds.findVariable( mainv.getShortName());
      return v.read();
    }

    public Array read(VariableDS mainv, CancelTask cancelTask, List section) throws IOException, InvalidRangeException {
      openDataset( cancelTask);
      if ((cancelTask != null) && cancelTask.isCancel())
        return null;

      Variable v = ds.findVariable( mainv.getShortName());
      return v.read(section);
    }

  }
}

/* Change History:
   $Log: Aggregation.java,v $
   Revision 1.2  2004/12/01 05:53:42  caron
   ncml pass 2, new convention parsing

   Revision 1.1  2004/11/21 01:16:47  caron
   ncml pass 1

*/