/* Copyright (C) 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.animation.distortion;

import artofillusion.*;
import artofillusion.animation.*;
import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;

/** The SkeletonShapeEditorWindow class represents the window for editing SkeletonShapeKeyframes. */

public class SkeletonShapeEditorWindow extends MeshEditorWindow implements EditingWindow
{   // TODO(MB) Allow 4 views
  private SkeletonShapeTrack track;
  private SkeletonShapeKeyframe keyframe;
  private int keyIndex;
  private double keyTime;
  private Smoothness keySmoothness;
  private BMenu editMenu, skeletonMenu;
  private BMenuItem skeletonMenuItem[], undoItem, redoItem, templateItem;
  private BMenuItem displayItem[], coordsItem[], axesItem;
  private Runnable onClose;
  //protected MeshViewer theView;   // TODO(MB) Allow 4 views

  public SkeletonShapeEditorWindow(EditingWindow parent, String title, SkeletonShapeTrack track, int keyIndex, Runnable onClose)
  {
//    super(parent, title, getEditMesh(((ObjectInfo) track.getParent()).object), onClose);
    super(parent, title,
        new ObjectInfo((Object3D)getEditMesh(((ObjectInfo) track.getParent()).object), new CoordinateSystem(), ""),
        onClose);
//    System.out.println("SkeletonShapeEditorWindow1 "+track);
    this.track = track;
    this.keyIndex = keyIndex;
    this.onClose = onClose;
    Timecourse tc = track.getTimecourse();
    keyframe = (SkeletonShapeKeyframe) tc.getValues()[keyIndex];
    keySmoothness = tc.getSmoothness()[keyIndex].duplicate();
    keyTime = tc.getTimes()[keyIndex];
    ObjectInfo info = ((ObjectInfo) track.getParent()).duplicate(oldObject.duplicate());
    
    //TODO(MB) Remove this menu creation
    
    MenuStructure ms = new MenuStructure();
    ms.loadStructure("SkeletonShape", false);
    menubar = ms.createMenuBar(this);
    setMenuBar(menubar);
    menuitemsCache = null;  // The menubar was rebuild so invalidate cache


//    FormContainer content = new FormContainer(new double [] {0.0, 1.0}, new double [] {0.0, 1.0, 0.0, 0.0});
//    setContent(content);
//    content.setDefaultLayout(new LayoutInfo(LayoutInfo.CENTER, LayoutInfo.BOTH, null, null));
//    content.add(helpText = new BLabel(), 0, 3, 2, 1);
//    FormContainer top = new FormContainer(new double [] {0.0, 1.0, 0.0, 0.0}, new double [] {1.0});
//    content.add(top, 1, 0);
//    RowContainer viewControls = new RowContainer();
//    top.add(viewControls, 0, 0);
//    if (oldObject instanceof SplineMesh)    // TODO(MB) More generic!
//      theView = new SplineMeshViewer(info, viewControls);
//    else
//      theView = new TriMeshViewer(info, viewControls);
//    content.add(theView, 1, 1, 1, 2);
//    top.add(Translate.button("ok", this, "doOk"), 2, 0);
//    top.add(Translate.button("cancel", this, "doCancel"), 3, 0);
//    content.add(tools = new ToolPalette(1, 3), 0, 0, 1, 2);
//    EditingTool metaTool, altTool;
//    tools.addTool(defaultTool = new SkeletonTool(this, false) {
//      protected void adjustMesh(Mesh newMesh)
//      {
//        adjustMeshForSkeleton();
//      }
//    });
//    tools.addTool(metaTool = new MoveViewTool(this));
//    tools.addTool(altTool = new RotateViewTool(this));
//    tools.selectTool(defaultTool);
//    theView.setMetaTool(metaTool);
//    theView.setAltTool(altTool);
//    UIUtilities.applyDefaultFont(content);
//    UIUtilities.applyDefaultBackground(content);
//    menubar = new BMenuBar();
//    setMenuBar(menubar);
//    createEditMenu();
//    createSkeletonMenu();
//    createViewMenu();
//    UIUtilities.recursivelyAddKeyPressedListeners(this);
    Dimension d1 = Toolkit.getDefaultToolkit().getScreenSize(), d2;
    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));
    tools.requestFocus();
    updateMenus();
    info.object.getSkeleton().copy(keyframe.getSkeleton());
    adjustMeshForSkeleton();
  }
  
  private static Mesh getEditMesh(Object3D object)
  {
    while (object instanceof ObjectWrapper)
      object = ((ObjectWrapper) object).getWrappedObject();
    if (object instanceof SplineMesh || object instanceof TriangleMesh)
      return (Mesh) object;
    return object.convertToTriangleMesh(Double.MAX_VALUE);
  }

//  void createEditMenu()
//  {
//    BMenuItem item;
//    
//    editMenu = Translate.menu("edit");
//    menubar.add(editMenu);
//    editMenu.add(undoItem = Translate.menuItem("undo", this, "undoCommand"));
//    editMenu.add(redoItem = Translate.menuItem("redo", this, "redoCommand"));
//    editMenu.addSeparator();
//    editMenu.add(Translate.menuItem("properties", this, "editProperties"));
//  }
//  
//  void createSkeletonMenu()
//  {
//    BMenuItem item;
//    skeletonMenu = Translate.menu("skeleton");
//    menubar.add(skeletonMenu);
//    skeletonMenuItem = new BMenuItem [1];
//    skeletonMenu.add(skeletonMenuItem[0] = Translate.menuItem("editBone", this, "editJointCommand"));
//    skeletonMenu.add(Translate.menuItem("resetToDefaultPose", this, "resetCommand"));
//    skeletonMenu.add(item = Translate.menuItem("createPoseFromGestures", this, "createFromGesturesCommand"));
////    System.out.println("SkelShapeEdiWindow.createSkeletonMenu1 "+track);
////    System.out.println("SkelShapeEdiWindow.createSkeletonMenu2 "+track.getParent());
//    if (track != null)   // TODO(MB) Remove this hack
//    {
//      Object3D obj = ((ObjectInfo) track.getParent()).object;
//      item.setEnabled(Actor.getActor(obj) != null);
//    }
//  }

  /* EditingWindow methods. */

  public void setTool(EditingTool tool)
  {
    for(int i = 0; i < theView.length; ++i)
      theView[i].setTool(tool);
    
    currentTool = tool;
  }
  
  /** 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;
    else
      return;
    
//    SplineMesh obj = (SplineMesh) getCurrentView().getObject().object;

    // Skeleton Menu

    if ("editBone".equals(itemname))
    {
      Skeleton s = keyframe.getSkeleton();
      Joint selJoint = s.getJoint(getCurrentView().getSelectedJoint());

      mnitem.setEnabled(selJoint != null);
    }
    else super.updateMenuItem(item);

  }

  
//  public void updateMenus()
//  {
//    undoItem.setEnabled(undoStack.canUndo());
//    redoItem.setEnabled(undoStack.canRedo());
//    templateItem.setEnabled(getCurrentView().getTemplateImage() != null);
//    Skeleton s = keyframe.getSkeleton();
//    Joint selJoint = s.getJoint(getCurrentView().getSelectedJoint());
//    skeletonMenuItem[0].setEnabled(selJoint != null);
//  }
  
  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 ("properties".equals(command))
      editProperties();
    else if ("editBone".equals(command))
      editJointCommand();
//    else if ("resetToDefaultPose".equals(command))
//      resetCommand();
//    else if ("createPoseFromGestures".equals(command))
//      createFromGesturesCommand();
    setCursor(Cursor.getDefaultCursor());
  }

//  public void itemStateChanged(CommandEvent e)
//  {
//    Object source = e.getSource();
//    BCheckBoxMenuItem sourceitem = null;
//    String itemname = "";
//
//    super.itemStateChanged(e);
//  }
    
  public MenuWidget createMenuItem(MenuDescription menudesc)
  {
    MenuWidget w = super.createMenuItem(menudesc);
    if ((w != null) && "createPoseFromGestures".equals(((Widget)w).getName()))
    {
      Object3D obj = ((ObjectInfo) track.getParent()).object;
      ((BMenuItem)w).setEnabled(Actor.getActor(obj) != null);
    }
    return w;
  }

  
  public void doOk()
  {
    parentWindow.setUndoRecord(new UndoRecord(parentWindow, false, UndoRecord.COPY_TRACK, new Object [] {track, track.duplicate(track.getParent())}));
    keyframe.getSkeleton().copy(getCurrentView().getObject().getSkeleton());
    track.moveKeyframe(keyIndex, keyTime);
    track.getTimecourse().getSmoothness()[keyIndex] = keySmoothness;
    oldObject = null;
    dispose();
    if (onClose != null)
      onClose.run();
  }
  
  public void doCancel()
  {
    oldObject = null;
    dispose();
  }
  
  /** Adjust the mesh after the skeleton moves. */
  
  private void adjustMeshForSkeleton()
  {
    Object3D obj = ((ObjectInfo) track.getParent()).object;
    Actor actor = Actor.getActor(obj);
    Mesh mesh = (Mesh) getCurrentView().getObject().object;
    if (track.getUseGestures() && actor != null)
      actor.shapeMeshFromGestures((Object3D) mesh);
    else
      Skeleton.adjustMesh((Mesh)oldObject, mesh);
  }
  
  /** Reset the skeleton to its default pose. */
  
  public void resetToDefaultPoseCommand()  //(MB) Was: resetCommand()
  {
    Object3D obj = ((ObjectInfo) track.getParent()).object;
    Actor actor = Actor.getActor(obj);
    Skeleton defaultSkeleton;
    if (actor != null)
      defaultSkeleton = actor.getGesture(0).getSkeleton();
    else
      defaultSkeleton = obj.getSkeleton();
    Object3D editObj = getCurrentView().getObject().object;
    setUndoRecord(new UndoRecord(this, false, UndoRecord.COPY_OBJECT, new Object [] {editObj, editObj.duplicate()}));
    editObj.getSkeleton().copy(defaultSkeleton);
    adjustMeshForSkeleton();
    getCurrentView().objectChanged();
    getCurrentView().updateImage();
    getCurrentView().repaint();
  }
  
  /** Create a skeleton shape by blending gestures. */
  
  public void createPoseFromGesturesCommand()  //(MB) Was: createFromGesturesCommand()
  {
    ObjectInfo info = (ObjectInfo) track.getParent();
    final Actor actor = Actor.getActor(info.object);
    final Actor.ActorKeyframe key = new Actor.ActorKeyframe();
    new ActorEditorWindow(this, info, actor, key, new Runnable() {
      public void run()
      {
        Skeleton newSkeleton = ((Gesture) key.createObjectKeyframe(actor)).getSkeleton();
        Object3D editObj = getCurrentView().getObject().object;
        setUndoRecord(new UndoRecord(SkeletonShapeEditorWindow.this, false, UndoRecord.COPY_OBJECT, new Object [] {editObj, editObj.duplicate()}));
        editObj.getSkeleton().copy(newSkeleton);
        adjustMeshForSkeleton();
        getCurrentView().objectChanged();
        getCurrentView().updateImage();
        getCurrentView().repaint();
      }
    });
  }
  
  /** Allow the user to edit keyframe properties. */
  
  protected void editProperties()
  {
    Timecourse tc = track.getTimecourse();
    ValueField timeField = new ValueField(keyTime, ValueField.NONE, 5);
    ValueSlider s1Slider = new ValueSlider(0.0, 1.0, 100, keySmoothness.getLeftSmoothness());
    final ValueSlider s2Slider = new ValueSlider(0.0, 1.0, 100, keySmoothness.getRightSmoothness());
    final BCheckBox sameBox = new BCheckBox(Translate.text("separateSmoothness"), !keySmoothness.isForceSame());
    
    sameBox.addEventLink(ValueChangedEvent.class, new Object() {
      void processEvent()
      {
        s2Slider.setEnabled(sameBox.getState());
      }
    });
    s2Slider.setEnabled(sameBox.getState());
    ComponentsDialog dlg = new ComponentsDialog(this, Translate.text("editKeyframe"), new Widget [] {
      timeField, sameBox, new BLabel(Translate.text("Smoothness")+':'), s1Slider, s2Slider},
      new String [] {Translate.text("Time"), null, null, "("+Translate.text("left")+")",
      "("+Translate.text("right")+")"});
    if (!dlg.clickedOk())
      return;
    if (sameBox.getState())
      keySmoothness.setSmoothness(s1Slider.getValue(), s2Slider.getValue());
    else
      keySmoothness.setSmoothness(s1Slider.getValue());
    keyTime = timeField.getValue();
  }

  /** Given a list of deltas which will be added to the selected vertices, calculate the
      corresponding deltas for the unselected vertices according to the mesh tension. */
  
  public void adjustDeltas(Vec3 delta[])
  {
  }
  
  public MeshViewer createMeshViewer(ObjectInfo obj, RowContainer p)
  {
    if (oldObject instanceof SplineMesh)    // TODO(MB) More generic!
      return new SplineMeshViewer(obj, p);
    else
      return new TriMeshViewer(obj, p);
  }


  public void initBaseSelectionHolder(ObjectInfo obj)
  {}   // TODO(MB) !?
  
  public void initWindowMenus(ObjectInfo obj)
  {
//    menubar = new BMenuBar();
//    setMenuBar(menubar);
//    createEditMenu();
//    createSkeletonMenu();
//    createViewMenu();    
  }
  
  
  /** Create the toolbar panel on the left of an object editor. */
  
  public BorderContainer createToolbarPanel()
  {
    BorderContainer p4 = new BorderContainer();

    p4.add(tools = new ToolPalette(1, 3), BorderContainer.NORTH);
    tools.addTool(defaultTool = new SkeletonTool(this, false) {
      protected void adjustMesh(Mesh newMesh)
      {
        adjustMeshForSkeleton();
      }
    });
    tools.addTool(getMetaTool() /* MoveViewTool */);
    tools.addTool(getAltTool() /* RotateViewTool */);
    tools.selectTool(defaultTool);

    return p4;
  }

  protected MeshEditorWindow createNewViewNewSelection()
  { return null;}
}
