//
// MyRubberBandBoxRendererJ3D.java
//

/*
VisAD system for interactive analysis and visualization of numerical
data.  Copyright (C) 1996 - 2002 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 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
Library General Public License for more details.

You should have received a copy of the GNU Library 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 util;

import visad.*;
import visad.java3d.*;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Vector;
import java.util.Enumeration;
import java.rmi.*;

import javax.media.j3d.*;

/**
   RubberBandBoxRendererJ3D is the VisAD class for direct
   manipulation of rubber band boxes
*/
public class MyRubberBandBoxRendererJ3D extends DirectManipulationRendererJ3D {

  private RealType x = null;
  private RealType y = null;
  private RealTupleType xy = null;

  private int mouseModifiersMask  = 0;
  private int mouseModifiersValue = 0;

  private BranchGroup branch = null;
  private BranchGroup group  = null;
  //- TDR
  private boolean       keep_last_box   = false;
  private BranchGroup   last_group      = null;
  private GeometryArray last_geometry   = null;
  private Appearance    last_appearance = null;

  public  boolean enabled = true;
  public  boolean active  = true;

  /** this DirectManipulationRenderer is quite different - it does not
      render its data, but only place values into its DataReference
      on right mouse button release;
      it uses xarg and yarg to determine spatial ScalarMaps */
  public MyRubberBandBoxRendererJ3D (RealType xarg, RealType yarg) {
    this(xarg, yarg, 0, 0);
  }

  /** xarg and yarg determine spatial ScalarMaps;
      mmm and mmv determine whehter SHIFT or CTRL keys are required -
      this is needed since this is a greedy DirectManipulationRenderer
      that will grab any right mouse click (that intersects its 2-D
      sub-manifold) */
  public MyRubberBandBoxRendererJ3D (RealType xarg, RealType yarg, int mmm, int mmv) {
    super();
    x = xarg;
    y = yarg;
    mouseModifiersMask = mmm;
    mouseModifiersValue = mmv;
  }

  /** don't render - just return BranchGroup for scene graph to
      render rectangle into */
  public synchronized BranchGroup doTransform()
         throws VisADException, RemoteException {
    branch = new BranchGroup();
    branch.setCapability(BranchGroup.ALLOW_DETACH);
    branch.setCapability(Group.ALLOW_CHILDREN_READ);
    branch.setCapability(Group.ALLOW_CHILDREN_WRITE);
    branch.setCapability(Group.ALLOW_CHILDREN_EXTEND);

    // check type and maps for valid direct manipulation
    if (!getIsDirectManipulation()) {
      throw new BadDirectManipulationException(getWhyNotDirect() +
        ": DirectManipulationRendererJ3D.doTransform");
    }
    setBranch(branch);

    if (keep_last_box) { //-TDR
      if (last_group != null) last_group.detach();
      branch.addChild(last_group);
    }

    return branch;
  }

  /** for use in drag_direct */
  private transient DataDisplayLink link = null;
  private transient DataReference ref = null;

  private transient ScalarMap xmap = null;
  private transient ScalarMap ymap = null;

  float[] default_values;

  /** arrays of length one for inverseScaleValues */
  private float[] f = new float[1];
  private float[] d = new float[1];
  private float[] value = new float[2];

  /** information calculated by checkDirect */
  /** explanation for invalid use of DirectManipulationRenderer */
  private String whyNotDirect = null;
  /** dimension of direct manipulation
      (always 2 for RubberBandBoxRendererJ3D) */
  private int directManifoldDimension = 2;
  /** spatial DisplayTupleType other than
      DisplaySpatialCartesianTuple */
  private DisplayTupleType tuple;
  private CoordinateSystem tuplecs;

  private int xindex = -1;
  private int yindex = -1;
  private int otherindex = -1;
  private float othervalue;

  private byte red, green, blue; // default colors

  private float[][] first_x;
  private float[][] last_x;
  private float[][] clast_x;
  private float cum_lon;

  /** possible values for whyNotDirect */
  private final static String xandyNotMatch =
    "x and y spatial domains don't match";
  private final static String xandyNotSpatial =
    "x and y must be mapped to spatial";


  private boolean stop = false;

  public void checkDirect() throws VisADException, RemoteException {
    setIsDirectManipulation(false);

    DisplayImpl display = getDisplay();

    DataDisplayLink[] Links = getLinks();
    if (Links == null || Links.length == 0) {
      link = null;
      return;
    }
    link = Links[0];

    ref = link.getDataReference();
    default_values = link.getDefaultValues();

    xmap = null;
    ymap = null;
    Vector scalar_map_vector = display.getMapVector();
    Enumeration enum = scalar_map_vector.elements();
    while (enum.hasMoreElements()) {
      ScalarMap map = (ScalarMap) enum.nextElement();
      ScalarType real = map.getScalar();
      if (real.equals(x)) {
        DisplayRealType dreal = map.getDisplayScalar();
        DisplayTupleType t = dreal.getTuple();
        if (t != null &&
            (t.equals(Display.DisplaySpatialCartesianTuple) ||
             (t.getCoordinateSystem() != null &&
              t.getCoordinateSystem().getReference().equals(
              Display.DisplaySpatialCartesianTuple)))) {
          xmap = map;
          xindex = dreal.getTupleIndex();
          if (tuple == null) {
            tuple = t;
          }
          else if (!t.equals(tuple)) {
            whyNotDirect = xandyNotMatch;
            return;
          }
        }
      }
      if (real.equals(y)) {
        DisplayRealType dreal = map.getDisplayScalar();
        DisplayTupleType t = dreal.getTuple();
        if (t != null &&
            (t.equals(Display.DisplaySpatialCartesianTuple) ||
             (t.getCoordinateSystem() != null &&
              t.getCoordinateSystem().getReference().equals(
              Display.DisplaySpatialCartesianTuple)))) {
          ymap = map;
          yindex = dreal.getTupleIndex();
          if (tuple == null) {
            tuple = t;
          }
          else if (!t.equals(tuple)) {
            whyNotDirect = xandyNotMatch;
            return;
          }
        }
      }
    }

    if (xmap == null || ymap == null) {
      whyNotDirect = xandyNotSpatial;
      return;
    }

    xy = new RealTupleType(x, y);

    // get default value for other component of tuple
    otherindex = 3 - (xindex + yindex);
    DisplayRealType dreal = (DisplayRealType) tuple.getComponent(otherindex);
    int index = getDisplay().getDisplayScalarIndex(dreal);
    othervalue = (index > 0) ? default_values[index] :
                               (float) dreal.getDefaultValue();

    // get default colors
    index = getDisplay().getDisplayScalarIndex(Display.Red);
    float v = (index > 0) ? default_values[index] :
                           (float) Display.Red.getDefaultValue();
    red = ShadowType.floatToByte(v);
    index = getDisplay().getDisplayScalarIndex(Display.Green);
    v = (index > 0) ? default_values[index] :
                      (float) Display.Green.getDefaultValue();
    green = ShadowType.floatToByte(v);
    index = getDisplay().getDisplayScalarIndex(Display.Blue);
    v = (index > 0) ? default_values[index] :
                      (float) Display.Blue.getDefaultValue();
    blue = ShadowType.floatToByte(v);

    if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
      tuple = null;
      tuplecs = null;
    }
    else {
      tuplecs = tuple.getCoordinateSystem();
    }

    directManifoldDimension = 2;
    setIsDirectManipulation(true);
  }

  private int getDirectManifoldDimension() {
    return directManifoldDimension;
  }

  public String getWhyNotDirect() {
    return whyNotDirect;
  }

  public void addPoint(float[] x) throws VisADException {
    // may need to do this for performance
  }

// methods customized from DataRenderer:

  public CoordinateSystem getDisplayCoordinateSystem() {
    return tuplecs;
  }

  /** set spatialValues from ShadowType.doTransform */
  public synchronized void setSpatialValues(float[][] spatial_values) {
    // do nothing
  }

  /** check if ray intersects sub-manifold */
  public synchronized float checkClose(double[] origin, double[] direction) {
    if (!active) {
      return Float.MAX_VALUE;
    }
    int mouseModifiers = getLastMouseModifiers();
    if ((mouseModifiers & mouseModifiersMask) != mouseModifiersValue) {
      return Float.MAX_VALUE;
    }

    try {
      float r = findRayManifoldIntersection(true, origin, direction, tuple,
                                            otherindex, othervalue);
      if (r == r) {
        return 0.0f;
      }
      else {
        return Float.MAX_VALUE;
      }
    }
    catch (VisADException ex) {
      return Float.MAX_VALUE;
    }
  }

  /** mouse button released, ending direct manipulation */
  public synchronized void release_direct() {
    // set data in ref
    if (!enabled) return;
    if (group != null) group.detach();
    group = null;
    try {
      float[][] samples = new float[2][2];
      f[0] = first_x[xindex][0];
      d = xmap.inverseScaleValues(f);
      samples[0][0] = (float) d[0];
      f[0] = first_x[yindex][0];
      d = ymap.inverseScaleValues(f);
      samples[1][0] = (float) d[0];
      f[0] = last_x[xindex][0];
      d = xmap.inverseScaleValues(f);
      samples[0][1] = (float) d[0];
      f[0] = last_x[yindex][0];
      d = ymap.inverseScaleValues(f);
      samples[1][1] = (float) d[0];
      Gridded2DSet set = new Gridded2DSet(xy, samples, 2);
      ref.setData(set);
      link.clearData();
    } // end try
    catch (VisADException e) {
      // do nothing
      System.out.println("release_direct " + e);
      e.printStackTrace();
    }
    catch (RemoteException e) {
      // do nothing
      System.out.println("release_direct " + e);
      e.printStackTrace();
    }
  }

  public void stop_direct() {
    stop = true;
  }

  private static final int EDGE = 20;

  private static final float EPS = 0.005f;

  public synchronized void drag_direct(VisADRay ray, boolean first,
                                       int mouseModifiers) {
    if (ref == null) return;

    if (first) {
      stop = false;
    }
    else {
      if (stop) return;
    }

    double[] origin = ray.position;
    double[] direction = ray.vector;

    try {
      float r = findRayManifoldIntersection(true, origin, direction, tuple,
                                            otherindex, othervalue);
      if (r != r) {
        if (group != null) group.detach();
        return;
      }
      float[][] xx = {{(float) (origin[0] + r * direction[0])},
                      {(float) (origin[1] + r * direction[1])},
                      {(float) (origin[2] + r * direction[2])}};
      if (tuple != null) xx = tuplecs.fromReference(xx);

      if (first) {
        first_x = xx;
        cum_lon = 0.0f;
      }
      else if (Display.DisplaySpatialSphericalTuple.equals(tuple)) {
        float diff = xx[1][0] - clast_x[1][0];
        if (diff > 180.0f) diff -= 360.0f;
        else if (diff < -180.0f) diff += 360.0f;
        cum_lon += diff;
        if (cum_lon > 360.0f) cum_lon -= 360.0f;
        else if (cum_lon < -360.0f) cum_lon += 360.0f;
      }
      clast_x = xx;

      Vector vect = new Vector();
      f[0] = xx[xindex][0];
      d = xmap.inverseScaleValues(f);

      // WLH 31 Aug 2000
      Real rr = new Real(x, d[0]);
      Unit overrideUnit = xmap.getOverrideUnit();
      Unit rtunit = x.getDefaultUnit();
      // units not part of Time string
      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
          !RealType.Time.equals(x)) {
        double dval =  overrideUnit.toThis((double) d[0], rtunit);
        rr = new Real(x, dval, overrideUnit);
      }   
      String valueString = rr.toValueString();

      vect.addElement(x.getName() + " = " + valueString);
      f[0] = xx[yindex][0];
      d = ymap.inverseScaleValues(f);

      // WLH 31 Aug 2000
      rr = new Real(y, d[0]);
      overrideUnit = ymap.getOverrideUnit();
      rtunit = y.getDefaultUnit();
      // units not part of Time string
      if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
          !RealType.Time.equals(y)) {
        double dval =  overrideUnit.toThis((double) d[0], rtunit);
        rr = new Real(y, dval, overrideUnit);
      }
      valueString = rr.toValueString();

      valueString = new Real(y, d[0]).toValueString();
      vect.addElement(y.getName() + " = " + valueString);
      getDisplayRenderer().setCursorStringVector(vect);

      float[][] xxp = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
      xxp[otherindex][0] += EPS;
      if (tuplecs != null) xxp = tuplecs.toReference(xxp);
      float[][] xxm = {{xx[0][0]}, {xx[1][0]}, {xx[2][0]}};
      xxm[otherindex][0] -= EPS;
      if (tuplecs != null) xxm = tuplecs.toReference(xxm);
      double dot = (xxp[0][0] - xxm[0][0]) * direction[0] +
                   (xxp[1][0] - xxm[1][0]) * direction[1] +
                   (xxp[2][0] - xxm[2][0]) * direction[2];
      float abs = (float)
        Math.sqrt((xxp[0][0] - xxm[0][0]) * (xxp[0][0] - xxm[0][0]) +
                 (xxp[1][0] - xxm[1][0]) * (xxp[1][0] - xxm[1][0]) +
                 (xxp[2][0] - xxm[2][0]) * (xxp[2][0] - xxm[2][0]));
      float other_offset = EPS * (2.0f * EPS / abs);
      if (dot >= 0.0) other_offset = -other_offset;

      last_x =
        new float[][] {{clast_x[0][0]}, {clast_x[1][0]}, {clast_x[2][0]}};
      if (Display.DisplaySpatialSphericalTuple.equals(tuple) &&
          otherindex != 1) {
        if (last_x[1][0] < first_x[1][0] && cum_lon > 0.0f) {
          last_x[1][0] += 360.0;
        }
        else if (last_x[1][0] > first_x[1][0] && cum_lon < 0.0f) {
          last_x[1][0] -= 360.0;
        }
      }

      int npoints = 4 * EDGE + 1;
      float[][] c = new float[3][npoints];
      for (int i=0; i<EDGE; i++) {
        float a = ((float) i) / EDGE;
        float b = 1.0f - a;
        c[xindex][i] = b * first_x[xindex][0] + a * last_x[xindex][0];
        c[yindex][i] = first_x[yindex][0];
        c[otherindex][i] = first_x[otherindex][0] + other_offset;
        c[xindex][EDGE + i] = last_x[xindex][0];
        c[yindex][EDGE + i] = b * first_x[yindex][0] + a * last_x[yindex][0];
        c[otherindex][EDGE + i] = first_x[otherindex][0] + other_offset;
        c[xindex][2 * EDGE + i] = b * last_x[xindex][0] + a * first_x[xindex][0];
        c[yindex][2 * EDGE + i] = last_x[yindex][0];
        c[otherindex][2 * EDGE + i] = first_x[otherindex][0] + other_offset;
        c[xindex][3 * EDGE + i] = first_x[xindex][0];
        c[yindex][3 * EDGE + i] = b * last_x[yindex][0] + a * first_x[yindex][0];
        c[otherindex][3 * EDGE + i] = first_x[otherindex][0] + other_offset;
      }
      c[0][npoints - 1] = c[0][0];
      c[1][npoints - 1] = c[1][0];
      c[2][npoints - 1] = c[2][0];
      if (tuple != null) c = tuplecs.toReference(c);
      float[] coordinates = new float[3 * npoints];
      for (int i=0; i<npoints; i++) {
        int i3 = 3 * i;
        coordinates[i3] = c[0][i];
        coordinates[i3 + 1] = c[1][i];
        coordinates[i3 + 2] = c[2][i];
      }
      VisADLineStripArray array = new VisADLineStripArray();
      array.vertexCount = npoints;
      array.stripVertexCounts = new int[1];
      array.stripVertexCounts[0] = npoints;
      array.coordinates = coordinates;
      byte[] colors = new byte[3 * npoints];
      for (int i=0; i<npoints; i++) {
        int i3 = 3 * i;
        colors[i3] = red;
        colors[i3 + 1] = green;
        colors[i3 + 2] = blue;
      }
      array.colors = colors;
      array = (VisADLineStripArray) array.adjustSeam(this);

      DisplayImplJ3D display = (DisplayImplJ3D) getDisplay();
      GeometryArray geometry = display.makeGeometry(array);
  
      DataDisplayLink[] Links = getLinks();
      if (Links == null || Links.length == 0) {
        return;
      }
      DataDisplayLink link = Links[0];

      float[] default_values = link.getDefaultValues();
      GraphicsModeControl mode = (GraphicsModeControl)
        display.getGraphicsModeControl().clone();
      float pointSize =
        default_values[display.getDisplayScalarIndex(Display.PointSize)];
      float lineWidth =
        default_values[display.getDisplayScalarIndex(Display.LineWidth)];
      mode.setPointSize(pointSize, true);
      mode.setLineWidth(lineWidth, true);
      Appearance appearance =
        ShadowTypeJ3D.staticMakeAppearance(mode, null, null, geometry, false);

      if (group != null) group.detach();
      group = null;

      Shape3D shape = new Shape3D(geometry, appearance);
      group = new BranchGroup();
      group.setCapability(Group.ALLOW_CHILDREN_READ);
      group.setCapability(BranchGroup.ALLOW_DETACH);
      group.addChild(shape);

      //-- TDR
      if (keep_last_box) {
        last_group      = group;
        last_geometry   = geometry;
        last_appearance = appearance;
      }

      if (branch != null) branch.addChild(group);
    } // end try
    catch (VisADException e) {
      // do nothing
      System.out.println("drag_direct " + e);
      e.printStackTrace();
    }
  }

  public Object clone() {
    return new MyRubberBandBoxRendererJ3D(x, y, mouseModifiersMask,
                                        mouseModifiersValue);
  }


  //---------------------------------------------------------
  public void setKeepLastBoxOn(boolean keep) {
    //- default is false
    keep_last_box = keep;
  }
  public void removeLastBox() {
    if (last_group != null) {
      last_group.detach();
    }
  }
  public BranchGroup getLastBox() {
    Shape3D shape     = new Shape3D(last_geometry, last_appearance);
    BranchGroup group = new BranchGroup();
    group.setCapability(Group.ALLOW_CHILDREN_READ);
    group.setCapability(BranchGroup.ALLOW_DETACH);
    group.addChild(shape);
    return group;
  }
  public void setLastBox(BranchGroup box_bg) {
    if (last_group != null) {
      last_group.detach();
    }
    last_group = box_bg;
    branch.addChild(box_bg);
  }
  public void setLastBox(MyRubberBandBoxRendererJ3D rbbr) {
    BranchGroup box_bg = rbbr.getLastBox();
    if (last_group != null) {
      last_group.detach();
    }
    last_group = box_bg;
    branch.addChild(box_bg);
  }
  //-----------------------------------------------------------


  private static final int N = 64;

  /** test RubberBandBoxRendererJ3D */
  public static void main(String args[])
         throws VisADException, RemoteException {
    RealType x = RealType.getRealType("x");
    RealType y = RealType.getRealType("y");
    RealTupleType xy = new RealTupleType(x, y);

    RealType c = RealType.getRealType("c");
    FunctionType ft = new FunctionType(xy, c);

    // construct Java3D display and mappings
    DisplayImpl display = new DisplayImplJ3D("display1");
    if (args.length == 0 || args[0].equals("z")) {
      display.addMap(new ScalarMap(x, Display.XAxis));
      display.addMap(new ScalarMap(y, Display.YAxis));
    }
    else if (args[0].equals("x")) {
      display.addMap(new ScalarMap(x, Display.YAxis));
      display.addMap(new ScalarMap(y, Display.ZAxis));
    }
    else if (args[0].equals("y")) {
      display.addMap(new ScalarMap(x, Display.XAxis));
      display.addMap(new ScalarMap(y, Display.ZAxis));
    }
    else if (args[0].equals("radius")) {
      display.addMap(new ScalarMap(x, Display.Longitude));
      display.addMap(new ScalarMap(y, Display.Latitude));
    }
    else if (args[0].equals("lat")) {
      display.addMap(new ScalarMap(x, Display.Longitude));
      display.addMap(new ScalarMap(y, Display.Radius));
    }
    else if (args[0].equals("lon")) {
      display.addMap(new ScalarMap(x, Display.Latitude));
      display.addMap(new ScalarMap(y, Display.Radius));
    }
    else {
      display.addMap(new ScalarMap(x, Display.Longitude));
      display.addMap(new ScalarMap(y, Display.Latitude));
    }
    display.addMap(new ScalarMap(c, Display.RGB));

    Integer2DSet fset = new Integer2DSet(xy, N, N);
    FlatField field = new FlatField(ft, fset);
    float[][] values = new float[1][N * N];
    int k = 0;
    for (int i=0; i<N; i++) {
      for (int j=0; j<N; j++) {
        values[0][k++] = (i - N / 2) * (j - N / 2);
      }
    }
    field.setSamples(values);
    DataReferenceImpl field_ref = new DataReferenceImpl("field");
    field_ref.setData(field);
    display.addReference(field_ref);

    Gridded2DSet dummy_set = new Gridded2DSet(xy, null, 1);
    final DataReferenceImpl ref = new DataReferenceImpl("set");
    ref.setData(dummy_set);
    int m = (args.length > 1) ? InputEvent.CTRL_MASK : 0;
    final MyRubberBandBoxRendererJ3D rbbr = new MyRubberBandBoxRendererJ3D(x,y,m,m);
    display.addReferences(rbbr, ref);

    CellImpl cell = new CellImpl() {
      public void doAction() throws VisADException, RemoteException {
        Set set = (Set) ref.getData();
        float[][] samples = set.getSamples();
        if (samples != null) {
          System.out.println("box (" + samples[0][0] + ", " + samples[1][0] +
                             ") to (" + samples[0][1] + ", " + samples[1][1] + ")");
        }
      }
    };
    cell.addReference(ref);

    // create JFrame (i.e., a window) for display and slider
    JFrame frame = new JFrame("test MyRubberBandBoxRendererJ3D");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(0);}
    });

    // create JPanel in JFrame
    JPanel panel = new JPanel();
    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
    panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
    panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
    frame.getContentPane().add(panel);

    // add display to JPanel
    panel.add(display.getComponent());

    // set size of JFrame and make it visible
    frame.setSize(500, 500);
    frame.setVisible(true);
  }
}

