/* Copyright (C) 2002-2004 by Peter Eastman

   This program 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; either version 2 of the License, or (at your option) any later version.

   This program 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. */

package artofillusion;

import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.texture.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;
import java.util.Vector;

/** The TubeEditorWindow class represents the window for editing Tube objects. */

public class TubeEditorWindow extends CurveEditorWindow
{
  BCheckBoxMenuItem endsItem[];  // TODO(MB) Needed?

  public TubeEditorWindow(EditingWindow parent, String title, ObjectInfo obj, Runnable onClose)
  {
    super(parent, title, obj, onClose, 1); // The 1 is a dummy value

    MenuStructure ms = new MenuStructure();
    ms.loadStructure("Tube", false);
    menubar = ms.createMenuBar(this);
    setMenuBar(menubar);
    menuitemsCache = null;  // The menubar was rebuild so invalidate cache
    
    UIUtilities.recursivelyAddKeyPressedListeners(this);
    Dimension d1 = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension d2 = new Dimension((d1.width*3)/4, (d1.height*3)/4);
    setBounds(new Rectangle((d1.width-d2.width)/2, (d1.height-d2.height)/2, d2.width, d2.height));

    updateMenus();
  }

//  void createEditMenu()
//  {
//    editMenu = Translate.menu("edit");
//    menubar.add(editMenu);
//    editMenuItem = new MenuItem [2];
//    editMenu.add(Translate.menuItem("undo", this));
//    editMenu.add(Translate.menuItem("selectAll", this));
//    editMenu.add(editMenuItem[0] = Translate.menuItem("extendSelection", this));
//    editMenu.add(editMenuItem[1] = Translate.checkboxMenuItem("freehandSelection", this, false));
//    editMenu.addSeparator();
//    editMenu.add(Translate.menuItem("curveTension", this));
//  }
//
//  void createMeshMenu(Curve obj2)
//  {
//    MenuItem item;
//    Tube obj=(Tube) obj2;
//
//    meshMenu = Translate.menu("tube");
//    menubar.add(meshMenu);
//    meshMenuItem = new MenuItem [8];
//    meshMenu.add(meshMenuItem[0] = Translate.menuItem("deletePoints", this));
//    meshMenu.add(meshMenuItem[1] = Translate.menuItem("subdivide", this));
//    meshMenu.add(meshMenuItem[2] = Translate.menuItem("editPoints", this));
//    meshMenu.add(meshMenuItem[3] = Translate.menuItem("transformPoints", this));
//    meshMenu.add(meshMenuItem[4] = Translate.menuItem("randomize", this));
//    meshMenu.add(meshMenuItem[5] = Translate.menuItem("parameters", this));
//    meshMenu.add(meshMenuItem[6] = Translate.menuItem("thickness", this));
//    // Does not work: meshMenu.add(Translate.menuItem("centerTube", this));
//    meshMenu.addSeparator();
//    meshMenu.add(meshMenuItem[7] = Translate.menuItem("smoothness", this));
//    meshMenu.add(smoothMenu = Translate.menu("smoothingMethod"));
//    smoothItem = new CheckboxMenuItem [3];
//    smoothMenu.add(smoothItem[0] = Translate.checkboxMenuItem("none", this, obj.getSmoothingMethod() == Curve.NO_SMOOTHING));
//    smoothMenu.add(smoothItem[1] = Translate.checkboxMenuItem("interpolating", this, obj.getSmoothingMethod() == Curve.INTERPOLATING));
//    smoothMenu.add(smoothItem[2] = Translate.checkboxMenuItem("approximating", this, obj.getSmoothingMethod() == Curve.APPROXIMATING));
//    endsItem = new CheckboxMenuItem [3];
//    Menu endsMenu = Translate.menu("endsStyle");
//    meshMenu.add(endsMenu);
//    endsMenu.add(endsItem[0] = Translate.checkboxMenuItem("openEnds", this, obj.getEndsStyle() == Tube.OPEN_ENDS));
//    endsMenu.add(endsItem[1] = Translate.checkboxMenuItem("closedEnds", this, obj.getEndsStyle() == Tube.CLOSED_ENDS));
//    endsMenu.add(endsItem[2] = Translate.checkboxMenuItem("flatEnds", this, obj.getEndsStyle() == Tube.FLAT_ENDS));
//    if (ModellingApp.getPreferences().getObjectPreviewRenderer() != null)
//      {
//	meshMenu.addSeparator();
//	meshMenu.add(Translate.menuItem("renderPreview", this));
//      }
//  }
//
//  protected Menu createShowMenu()
//  {
//    Menu menu = Translate.menu("show");
//    menu.add(Translate.checkboxMenuItem("curve", this, getCurrentView().getMeshVisible()));
//    menu.add(Translate.checkboxMenuItem("surface", this, getCurrentView().getSurfaceVisible()));
//    menu.add(Translate.checkboxMenuItem("entireScene", this, getCurrentView().getSceneVisible()));
//    return menu;
//  }
  /** Updates a single menu item
    (setting enabled/disabled, (un)checked ...). */
  protected void updateMenuItem(MenuWidget item)
  {
    String itemname = ((Widget)item).getName();
    BCheckBoxMenuItem cbitem = null;
    BMenuItem mnitem = null;
    if (item instanceof BCheckBoxMenuItem)
      cbitem = (BCheckBoxMenuItem)item;
    else if (item instanceof BMenuItem)
      mnitem = (BMenuItem)item;
    
    Tube obj = (Tube) getCurrentView().getObject().object;

    // Mesh Menu

    if ("parameters".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("thickness".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    else if ("centerTube".equals(itemname))  // TODO(MB) Does not work ?
      mnitem.setEnabled(isSomethingSelected());
    else if ("smoothness".equals(itemname))
      mnitem.setEnabled(isSomethingSelected());
    
    // Ends Style Menu in Mesh Menu
    
    else if ("openEnds".equals(itemname))
      cbitem.setState(obj.getEndsStyle() == Tube.OPEN_ENDS);
    else if ("closedEnds".equals(itemname))
      cbitem.setState(obj.getEndsStyle() == Tube.CLOSED_ENDS);
    else if ("flatEnds".equals(itemname))
      cbitem.setState(obj.getEndsStyle() == Tube.FLAT_ENDS);

    else super.updateMenuItem(item);
  }

  /*
  public void updateMenus()
  {
    boolean selected[] = ((CurveViewer) theView).getSelection();
    int i;

    for (i = 0; i < selected.length && !selected[i]; i++);
    if (i < selected.length)
    {
      editMenuItem[0].setEnabled(true);
      for (i = 0; i < meshMenuItem.length; i++)
        meshMenuItem[i].setEnabled(true);
    }
    else
    {
      editMenuItem[0].setEnabled(false);
      for (i = 0; i < meshMenuItem.length; i++)
        meshMenuItem[i].setEnabled(false);
    }
    templateItem.setEnabled(theView.getTemplateImage() != null);
  }
  */
  
  public void itemStateChanged(CommandEvent e)
  {
    Object source = e.getSource();
    BCheckBoxMenuItem sourceitem = null;
    String itemname = "";
    int i;

    if (source instanceof BCheckBoxMenuItem)
    {
      sourceitem = (BCheckBoxMenuItem)source;
      itemname = sourceitem.getName();
    }
    
    final String[] ENDSSTYLES= new String[]{"openEnds", "closedEnds",
                                             "flatEnds"};

    i = Utilities.findIndexEqual(ENDSSTYLES, itemname);
    if (i > -1)
    {
      ((Tube) getCurrentView().getObject().object).setEndsStyle(i);
      updateMenus();
      object3DChangedDuringEditor();
      return;
    }

    super.itemStateChanged(e);
  }

  public void actionPerformed(CommandEvent e)
  {
    String command = e.getActionCommand();

    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    if (handleMenuAction(command)) // Try to call the command by reflection
      ;  // Do nothing
//    else if (command.equals("undo"))
//      undoCommand();
//    else if (command.equals("selectAll"))
//      selectAllCommand();
//    else if (command.equals("extendSelection"))
//      extendSelectionCommand();
    else if (command.equals("deletePoints"))
      deleteCommand();
//    else if (command.equals("subdivide"))
//      subdivideCommand();
    else if (command.equals("editPoints"))
      setPointsCommand();
//    else if (command.equals("transformPoints"))
//      transformPointsCommand();
//    else if (command.equals("randomize"))
//      randomizeCommand();
    else if (command.equals("parameters"))
      setParametersCommand();
    else if (command.equals("thickness"))
      setThicknessCommand();
    else if (command.equals("centerTube"))
      centerCommand();
    else if (command.equals("smoothness"))
      setSmoothnessCommand();
    else if (command.equals("curveTension"))
      setTensionCommand();
    else if (command.equals("closedEnds"))
      toggleClosedCommand();
    else if (command.equals("renderPreview"))
      getCurrentView().previewObject();
    else if (command.equals("grid"))
      setGridCommand();
    else if (command.equals("fourViews"))
      toggleViewsCommand();
    else if (command.equals("showTemplate"))
      {
        boolean wasShown = getCurrentView().getTemplateShown();
        for(int i = 0; i < theView.length; ++i)
          theView[i].setShowTemplate(!wasShown);
        updateMenus();
        updateImage();
      }
//    else if (command.equals("setTemplate"))
//      setTemplateCommand();
    setCursor(Cursor.getDefaultCursor());
  }

  /** Creates a new view, containing the same Object3D with a
   * new VertexSelection.
   */
  protected MeshEditorWindow createNewViewNewSelection()
  {
    TubeEditorWindow w = new TubeEditorWindow(parentWindow, getTitle(), objInConstructor, onClose);
    // w.theView.setObject(theView.getObject().getObject3D());  TODO(MB) ??? Something with editGesture ???
    return w;
  }

  /** Create a MeshViewer which is appropriate for a particular editor window.
    Here a TubeViewer.*/
  
  public MeshViewer createMeshViewer(ObjectInfo obj, RowContainer p)
  {
    return new TubeViewer(obj,  p);
  }
  
  /** Create the toolbar panel on the left of an object editor. */
  
  public BorderContainer createToolbarPanel()
  {
    BorderContainer p4 = new BorderContainer();

    p4.add(tools = new ToolPalette(1, 7), BorderContainer.NORTH);
    tools.addTool(defaultTool = new ReshapeMeshTool(this));
    tools.addTool(new ScaleMeshTool(this));
    tools.addTool(new RotateMeshTool(this, false));
    tools.addTool(new SkewMeshTool(this));
    tools.addTool(new TaperMeshTool(this));
    tools.addTool(getMetaTool() /* MoveViewTool */);
    tools.addTool(getAltTool() /* RotateViewTool */);
    tools.selectTool(defaultTool);

    return p4;
  }
  
  /** Delete the selected vertices. */

  public void deleteCommand()
  {
    int i, j, num = 0;
    Tube theTube = (Tube) getCurrentView().getObject().object;
    boolean selected[] = ((TubeViewer) getCurrentView()).getSelection(), newsel[];
    MeshVertex vt[] = theTube.getVertices(), newv[];
    double t[] = theTube.getThickness(), newt[];
    float s[] = theTube.getSmoothness(), news[];
    Vec3 v[];

    for (i = 0; i < selected.length; i++)
      if (selected[i])
	num++;
    if (num == 0)
      return;
    if (theTube.getEndsStyle() != Tube.CLOSED_ENDS && selected.length-num < 2)
    {
      new BStandardDialog("", Translate.text("tubeNeeds2Points"), BStandardDialog.INFORMATION).showMessageDialog(this);
      return;
    }
    if (theTube.getEndsStyle() == Tube.CLOSED_ENDS && selected.length-num < 3)
    {
      new BStandardDialog("", Translate.text("tubeNeeds3Points"), BStandardDialog.INFORMATION).showMessageDialog(this);
      return;
    }
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theTube, theTube.duplicate()}));
    newv = new MeshVertex [vt.length-num];
    newt = new double [vt.length-num];
    news = new float [vt.length-num];
    newsel = new boolean [vt.length-num];
    for (i = 0, j = 0; i < vt.length; i++)
    {
      if (!selected[i])
      {
        newsel[j] = selected[i];
        newt[j] = t[i];
        news[j] = s[i];
        newv[j++] = vt[i];
      }
    }
    theTube.setShape(newv, news, newt);
    object3DChangedDuringEditor();
    ((TubeViewer) getCurrentView()).setSelection(newsel);
    ((CurveViewer) getCurrentView()).informSelectionChanged(this,
        ModelEvent.CHANGEDUR_OBJECTEDITOR);    
  }

  /** Subdivide the tube between the selected vertices. */

  public void subdivideCommand()
  {
    Tube theTube = (Tube) getCurrentView().getObject().object;
    MeshVertex vt[] = theTube.getVertices(), newpos[];
    float s[] = theTube.getSmoothness(), news[];
    double t[] = theTube.getThickness(), newt[];
    int numParam = (theTube.getParameters() == null ? 0 : theTube.getParameters().length);
    double param[][], newparam[][], paramTemp[] = new double [numParam];
    boolean selected[] = ((TubeViewer) getCurrentView()).getSelection(), newsel[], split[];
    int i, j, p1, p3, p4, splitcount = 0, method = theTube.getSmoothingMethod();

    // Record parameter values.
    
    param = new double [vt.length][numParam];
    ParameterValue paramValue[] = theTube.getParameterValues();
    for (i = 0; i < numParam; i++)
    {
      if (paramValue[i] instanceof VertexParameterValue)
      {
        double val[] = ((VertexParameterValue) paramValue[i]).getValue();
        for (j = 0; j < val.length; j++)
          param[j][i] = val[j];
      }
    }
    
    // Determine which parts need to be subdivided.

    if (theTube.getEndsStyle() == Tube.CLOSED_ENDS)
      split = new boolean [vt.length];
    else
      split = new boolean [vt.length-1];
    for (i = 0; i < split.length; i++)
      if (selected[i] && selected[(i+1)%selected.length])
      {
        split[i] = true;
        splitcount++;
      }
    newpos = new MeshVertex [vt.length+splitcount];
    news = new float [vt.length+splitcount];
    newt = new double [vt.length+splitcount];
    newparam = new double [vt.length+splitcount][numParam];
    newsel = new boolean [vt.length+splitcount];

    // Do the subdivision.

    for (i = 0, j = 0; i < split.length; i++)
    {
      newsel[j] = selected[i];
      p1 = i-1;
      if (p1 < 0)
      {
        if (theTube.getEndsStyle() == Tube.CLOSED_ENDS)
          p1 = vt.length-1;
        else
          p1 = 0;
      }
      if (i < vt.length-1)
        p3 = i+1;
      else
      {
        if (theTube.getEndsStyle() == Tube.CLOSED_ENDS)
          p3 = 0;
        else
          p3 = vt.length-1;
      }
      if (selected[i] && method == Mesh.APPROXIMATING)
      {
        newpos[j] = SplineMesh.calcApproxPoint(vt, s, param, paramTemp, p1, i, p3);
        newt[j] = Tube.calcApproxThickness(t, s, p1, i, p3);
        for (int k = 0; k < numParam; k++)
          newparam[j][k] = paramTemp[k];
      }
      else
      {
        newpos[j] = vt[i];
        newt[j] = t[i];
        newparam[j] = param[i];
      }
      if (selected[i])
        news[j] = Math.min(s[i]*2.0f, 1.0f);
      else
        news[j] = s[i];
      if (!split[i])
      {
        j++;
        continue;
      }
      if (method == Mesh.NO_SMOOTHING)
      {
        newpos[j+1] = MeshVertex.blend(vt[i], vt[p3], 0.5, 0.5);
        for (int k = 0; k < numParam; k++)
          newparam[j+1][k] = 0.5*(param[i][k]+param[p3][k]);
      }
      else if (method == Mesh.INTERPOLATING)
      {
        if (i < vt.length-2)
          p4 = i+2;
        else
        {
          if (theTube.getEndsStyle() == Tube.CLOSED_ENDS)
            p4 = (i+2)%vt.length;
          else
            p4 = vt.length-1;
        }
        newpos[j+1] = SplineMesh.calcInterpPoint(vt, s, param, paramTemp, p1, i, p3, p4);
        newt[j+1] = Tube.calcInterpThickness(t, s, p1, i, p3, p4);
        for (int k = 0; k < numParam; k++)
          newparam[j+1][k] = paramTemp[k];
      }
      else
      {
        newpos[j+1] = MeshVertex.blend(vt[i], vt[p3], 0.5, 0.5);
        newt[j+1] = 0.5*(t[i]+t[p3]);
        for (int k = 0; k < numParam; k++)
          newparam[j+1][k] = 0.5*(param[i][k]+param[p3][k]);
      }
      news[j+1] = 1.0f;
      newsel[j+1] = true;
      j += 2;
    }
    if (theTube.getEndsStyle() != Tube.CLOSED_ENDS)
    {
      newpos[0] = vt[0];
      newpos[j] = vt[i];
      newt[0] = t[0];
      newt[j] = t[i];
      news[j] = s[i];
      newparam[0] = param[0];
      newparam[j] = param[i];
      newsel[j] = selected[i];
    }
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theTube, theTube.duplicate()}));
    theTube.setShape(newpos, news, newt);
    for (i = 0; i < numParam; i++)
    {
      if (paramValue[i] instanceof VertexParameterValue)
      {
        double val[] = new double [newpos.length];
        for (j = 0; j < val.length; j++)
          val[j] = newparam[j][i];
        paramValue[i] = new VertexParameterValue(val);
      }
    }
    theTube.setParameterValues(paramValue);
    ((TubeViewer) getCurrentView()).setSelection(newsel);
    ((CurveViewer) getCurrentView()).informSelectionChanged(this,
        ModelEvent.CHANGEDUR_OBJECTEDITOR);    
    object3DChangedDuringEditor();
  }

  /** Allow the user to set the thickness for selected vertices. */

  public void setThicknessCommand()
  {
    Tube theTube = (Tube) getCurrentView().getObject().object;
    double thickness[] = theTube.getThickness(), initial = -1;
    int selected[] = getCurrentView().getSelectionDistance();

    for (int i = 0; i < selected.length; i++)
      if (selected[i] == 0)
      {
        if (initial == -1)
          initial = thickness[i];
        else if (initial != thickness[i])
          initial = Double.NaN;
      }
    if (initial == -1)
      return;  // No selected vertices.
    ValueField thicknessField = new ValueField(initial, ValueField.NONNEGATIVE, 5);
    ComponentsDialog dlg = new ComponentsDialog(this, Translate.text("setThicknessTitle"),
      new Widget [] {thicknessField}, new String [] {Translate.text("Thickness")});
    if (!dlg.clickedOk() || Double.isNaN(thicknessField.getValue()))
      return;
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {theTube, theTube.duplicate()}));
    for (int i = 0; i < selected.length; i++)
      if (selected[i] == 0)
        thickness[i] = thicknessField.getValue();
    theTube.setThickness(thickness);
    object3DChangedDuringEditor();
  }
}