/* Copyright (C) 1999-2003 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.animation.*;
import artofillusion.math.*;
import artofillusion.object.*;
import buoy.widget.*;
import java.awt.*;
import java.util.*;

/** MeshViewer is an abstract subclass of ViewerCanvas used for displaying Mesh objects. */

public abstract class MeshViewer extends ObjectViewer
{
  public static final int HANDLE_SIZE = 5;
  
//  /** Do not broadcast drawing operations. */
//  public static final int BROADCAST_NO = 0;
//  /** Broadcast drawing operations if appropriate variable (broadcastDragging)
//    is set true. */
//  public static final int BROADCAST_IFSET = 1;
//  /** Broadcast drawing operations always. */
//  public static final int BROADCAST_ALWAYS = 2;

  protected boolean showMesh, showSurface, showSkeleton;
  
  /** Should the view be synchronized while a dragging operation is performed
    in another one? */
  protected boolean draggingAutosync;
//  private int selectedJoint;
//  private boolean detachSkeleton;   // (MB) now all three handled by SkeletonControlHolder
//  private Vector lockedJoints;

  private static boolean lastShowMesh = true;
  private static boolean lastShowSurface = true;
  private static boolean lastShowSkeleton = true;
  private static boolean lastDraggingAutosync = true;

  int selectionDistance[], maxDistance, deselect;
  protected MeshSelectionHolder selHolder;
  protected SkeletonControlHolder skelControlHolder;

  
  public MeshViewer(ObjectInfo obj, RowContainer p)
  {
    super(obj, p);
    //lockedJoints = new Vector();
    //Skeleton s = ((Mesh) obj.object).getSkeleton();
    setMeshVisible(lastShowMesh);
    setSurfaceVisible(lastShowSurface);
    setSkeletonVisible(lastShowSkeleton);
    setDraggingAutosync(lastDraggingAutosync);
    // For convenience create a default selection and
    // skeleton control holder for the view
    setSelectionHolder(new MeshSelectionHolder(obj)); 
    setSkeletonControlHolder(new SkeletonControlHolder(obj));
  }
  
  public void dispose()
  {
    setSelectionHolder(null);
    setSkeletonControlHolder(null);
    super.dispose();
  }

  public int getSelectionMode()
  {
    return getSelectionHolder().getSelectionMode();
  }

  /** Get an array of flags telling which parts of the mesh are currently selected.  Depending
    on the current selection mode, these flags may correspond to vertices, edges, or faces. */

  public boolean[] getSelection()
  {
    return getSelectionHolder().getSelection();
  }
  
  protected boolean handleModelEvent(ModelEvent event)
  {
    if (!super.handleModelEvent(event))
    {
      if (event.hasKey("skeletoncontrol changed") && event.getCause() != this &&
            isVisible())
      {
        // TODO(MB) "if (event.getCause() != this)"   Change when SkeletonTool sends events
        updateImage();
        repaint();
      }
      else if (event.getSource() == theObject)
      {
        if (event.hasKey("object3d dragging") && event.getCause() != this &&
            isVisible() && draggingAutosync)
        {
          Vec3[] v = (Vec3[])event.getValue("vertices");
          Graphics g = getComponent().getGraphics();  //TODO(MB) Remove this hack
          Camera cam = getCamera();
          drawImage(g);
          g.setColor(Color.gray);
          drawDraggedSelection(g, cam, v, false);
          g.dispose();
        }
        else if (event.hasKey("object3d changed") &&
            event.getValue("happened during") == ModelEvent.CHANGEDUR_OBJECTEDITORDRAG &&
            event.getCause() != this && isVisible() && draggingAutosync)
        {    // TODO(MB) Move to ObjectViewer, needs move of draggingAutosync
          updateInternalState();
          updateImage();
          repaint();
        }
        else
          return false;
      }
      else if (event.getSource() == selHolder)
      {
        System.out.println("MeshViewer.handleModelEvent selection changed1");
        if (event.hasKey("selection changed") /*&& event.getCause() != this*/ )
        {
          invalidateSelectionDistance();
          System.out.println("MeshViewer.handleModelEvent selection changed2");
          if (isVisible())
          {
            System.out.println("MeshViewer.handleModelEvent selection changed3");
            updateImage();
            repaint();
          }
        }
      }
      else
        return false;
    }
    return true;
  }


  /** Sets a selection holder and increments its viewer reference count. */
  public void setSelectionHolder(MeshSelectionHolder holder)
  {
    if (selHolder != null)
    {
      selHolder.decRefCount();
      selHolder.getModelEvent().removeListener(this);
    }
      
    selHolder = holder;
    if (selHolder != null)
    {
      selHolder.incRefCount();
      selHolder.getModelEvent().addListener(this);

      // TODO(MB) More stable solution
      selHolder.setSelectionMode(0);
      
      Mesh mesh = (Mesh) theObject;
      if (selHolder.getSelection() == null ||
          selHolder.getSelection().length != mesh.getVertices().length)
      selHolder.setSelection(new boolean [mesh.getVertices().length]);
    }
  }
  
  /** Get the selection holder of the view. */
  public MeshSelectionHolder getSelectionHolder()
  {
    return selHolder;
  }
  
  /** Sets a skeleton control holder. */
  public void setSkeletonControlHolder(SkeletonControlHolder holder)
  {
    if (skelControlHolder != null)
      skelControlHolder.getModelEvent().removeListener(this);
      
    skelControlHolder = holder;
    if (skelControlHolder != null)
      skelControlHolder.getModelEvent().addListener(this);
  }
  
  /** Get the skeleton control holder of the view. */
  public SkeletonControlHolder getSkeletonControlHolder()
  {
    return skelControlHolder;
  }
  
  /** Fire an event to inform about selection changes. */
  public void informSelectionChanged(Object cause, String during)
  {
    if (selHolder != null)
      selHolder.informChanged(cause, during);
  }

  
  /** Called by {@link drawDraggedSelection} to broadcast a dragging
    operation if needed. */
  protected void broadcastDraggedSelection(Vec3 v[], boolean broadcast)
  {
    if (broadcast)
      theObject.informDragging(this, v);
  }
  
  /** Draw any parts of the mesh which have been dragged.
    @param broadcast controls if a "object3d dragging" event should be send
              through Object3D.*/
  public abstract void drawDraggedSelection(Graphics g, Camera cam, Vec3 v[],
    boolean broadcast);

  /** Get the distance of each vertex from a selected vertex.  This is 0 for a selected
      vertex, 1 for a vertex adjacent to a selected one, etc., up to a specified maximum
      distance.  For vertices more than the maximum distance for a selected one, it is -1. */

  public int[] getSelectionDistance()
  {
    if (maxDistance != MeshEditorWindow.getTensionDistance() ||
        selectionDistance == null)
      findSelectionDistance();
    return selectionDistance;
  }

  /** Invalidate selection distance. It is newly calculated when
    {@link #getSelectionDistance} or {@link #findSelectionDistance}
    is called. */
  
  public void invalidateSelectionDistance()
  {
    selectionDistance = null;
  }

  /** Calculate the distance (in edges) between each vertex and the nearest selected vertex. */

  protected abstract void findSelectionDistance();
  
  /** Set the Mesh object for this viewer. */
  

  /** Set the Mesh object for this viewer. */
  public abstract void setMesh(Mesh mesh);

  /** Refreshes internal data if the viewed object changed. */
  public void updateInternalState()
  {
    setMesh((Mesh)theObject);
  }
  
  /** Copies the settings of the view from another one
    (render mode, showing control mesh, surfaces..., coordinate system).
    It does not refresh the view.
   */
  public void copyViewSettings(MeshViewer v)
  {
    setRenderMode(v.getRenderMode());

    setMeshVisible(v.getMeshVisible());
    setSurfaceVisible(v.getSurfaceVisible());
    setSkeletonVisible(v.getSkeletonVisible());
    setSceneVisible(v.getSceneVisible());

    setUseWorldCoords(v.getUseWorldCoords());
  }  
    

  /** Get the ID of the selected joint. Delegated to SkeletonControlHolder.*/
  public int getSelectedJoint()
  {
    return getSkeletonControlHolder().getSelectedJoint();
  }

  /** Set the selected joint (and send appropriate events).
    Delegated to SkeletonControlHolder.*/
  
  public void setSelectedJoint(int id)
  {
    getSkeletonControlHolder().setSelectedJointE(id);
  }

  /** Get an array of size [# joints in skeleton] specifying which ones
    are locked. Delegated to SkeletonControlHolder.*/
  
  public boolean [] getLockedJoints()
  {
    Skeleton s = ((Mesh) theObject).getSkeleton();
    if (s == null)
      return new boolean [0];
    return getSkeletonControlHolder().getLockedJoints(s);
  }
  
  /** Determine whether a particular joint is locked. 
    Delegated to SkeletonControlHolder. */
  
  public boolean isJointLocked(int id)
  {
    return getSkeletonControlHolder().isJointLocked(id);
  }

  /** Lock the joint with the specified ID.
    Delegated to SkeletonControlHolder. */
  
  public void lockJoint(int id)
  {
    SkeletonControlHolder sch = getSkeletonControlHolder();
    sch.lockJoint(id);
    sch.informChanged(this, ModelEvent.CHANGEDUR_DONTKNOW);  // TODO(MB) Move to SkeletonTool     
  }
  
  /** Unlock the joint with the specified ID.
    Delegated to SkeletonControlHolder. */
  
  public void unlockJoint(int id)
  {
    SkeletonControlHolder sch = getSkeletonControlHolder();
    sch.unlockJoint(id);
    sch.informChanged(this, ModelEvent.CHANGEDUR_DONTKNOW);  // TODO(MB) Move to SkeletonTool     
  }

  /** Get whether the control mesh is visible. */

  public boolean getMeshVisible()
  {
    return showMesh;
  }

  /** Set whether the control mesh is visible. */

  public void setMeshVisible(boolean visible)
  {
    lastShowMesh = showMesh = visible;
  }

  /** Get whether the surface is visible. */

  public boolean getSurfaceVisible()
  {
    return showSurface;
  }

  /** Set whether the surface is visible. */

  public void setSurfaceVisible(boolean visible)
  {
    lastShowSurface = showSurface = visible;
  }
  
  /** Get whether dragging is synchronized automatically between views. */

  public boolean getDraggingAutosync()
  {
    return draggingAutosync;
  }

  /** Set whether dragging is synchronized automatically between views. */

  public void setDraggingAutosync(boolean das)
  {
    lastDraggingAutosync = draggingAutosync = das;
  }

  /** Get whether the skeleton is visible. */

  public boolean getSkeletonVisible()
  {
    return showSkeleton;
  }

  /** Set whether the skeleton is visible. */

  public void setSkeletonVisible(boolean visible)
  {
    lastShowSkeleton = showSkeleton = visible;
  }

  /** Get whether the mesh is detached from the skeleton. */

  public boolean getSkeletonDetached()
  {
    Skeleton skel=((Mesh)theObject).getSkeleton();
    if (skel == null)
      return true;

    return skel.getSkeletonDetached();
  }

  /** Set whether the mesh is detached from the skeleton. */

  public void setSkeletonDetached(boolean detached)
  {
    Skeleton skel=((Mesh)theObject).getSkeleton();
    if (skel == null)
      return;

    skel.setSkeletonDetached(detached);
  }
}