/* SkeletonTool is an EditingTool used for manipulating the skeletons of objects. */

/* Copyright (C) 1999-2002 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.*;
import artofillusion.math.*;
import artofillusion.object.*;
import artofillusion.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.Image;
import java.awt.Point;
import java.util.Vector;

/** An additional tool for the TriMeshEditorWindow to create "free" vertices
  and subdivide edges and faces. */
public class CreateVertexTool_ED extends EditingTool
{
  /** A "structure" to hold informations returned by
    {@link #findClickTarget()}. */
  public static class ClickTarget
  {
    public static final int TYPE_VERTEX = 1;
    public static final int TYPE_EDGE = 2;
    public static final int TYPE_FACE = 3;
    
    /** Type of clicked object, one of the TYPE_* constants. */
    public int type;
    /** Index of clicked object in the vertex/edge/face array of the mesh. */
    public int index;
    /** Clicked position in object coordinates. */
    public Vec3 clickPos;
    public double u, v;
    
    public ClickTarget()
    {
      this.index = -1;
    }
  }

  static Image icon, selectedIcon;
  Point clickPoint;
  ClickTarget target;
  boolean dragged;
  /** Index of the vertex to move. */
  int vertexToMove;
  // Mesh oldMesh, mesh;
  UndoRecord undo;
  boolean freeVert=true, moveVert=true, edgeSubdiv=true, faceSubdiv=true;
  
  
  public CreateVertexTool_ED(EditingWindow fr)  //k, MeshViewer theView)
  {
    super(fr);
    icon = loadImage("sphere.gif");  // TODO(MB) Own icon
    selectedIcon = loadImage("selected/sphere.gif");
  }

  public void activate()
  {
    super.activate();
    // TODO(MB) theWindow.setHelpText(Translate.text("skeletonTool.helpText"));
  }

  public int whichClicks()
  {
    return ALL_CLICKS;
  }

  public Image getIcon()
  {
    return icon;
  }

  public Image getSelectedIcon()
  {
    return selectedIcon;
  }
  
  /* Allow the user to set options. */

  public void iconDoubleClicked()
  {
    BCheckBox freeVertBox = new BCheckBox("Create free vertex", freeVert);  // TODO(MB) Localize
    BCheckBox moveVertBox = new BCheckBox("Move vertex", moveVert);
    BCheckBox edgeSubdivBox = new BCheckBox("Subdivide edge", edgeSubdiv);
    BCheckBox faceSubdivBox = new BCheckBox("Subdivide face", faceSubdiv);

    ComponentsDialog dlg = new ComponentsDialog(theFrame, "Options for Create Vertex Tool:",
		new Widget [] {freeVertBox, moveVertBox,
                    edgeSubdivBox, faceSubdivBox},
                    new String [] {"", "", "", ""});
    if (!dlg.clickedOk())
      return;
                    
    freeVert = freeVertBox.getState();
    moveVert = moveVertBox.getState();
    edgeSubdiv = edgeSubdivBox.getState();
    faceSubdiv = faceSubdivBox.getState();
  }  
  
  /** Determine which vertex, edge, or face (depending on the settings in the
     edit dialog) the mouse was clicked on.  If the click was on top of
     multiple objects, priority is given to ones which are in front.  If the
     click is not over any object, the 'index' member of the returned
     ClickTarget is -1.
   */
  
  protected ClickTarget findClickTarget(Point pos, TriMeshViewer theView)
  {
    TriangleMesh theMesh = (TriangleMesh)theView.getObject().object;
    TriangleMesh.Vertex vt[] = (TriangleMesh.Vertex []) theMesh.getVertices();
    TriangleMesh.Edge ed[] = theMesh.getEdges();
    TriangleMesh.Face fc[] = theMesh.getFaces();
    
    boolean[] visible = theView.getVisible();
    Point[] screenVert = theView.getScreenVert();
    final int HANDLE_SIZE = theView.HANDLE_SIZE;
    Camera theCamera = theView.getCamera();
    
    ClickTarget result = new ClickTarget();
    
    double u, v, w, z, closestz = Double.MAX_VALUE;
    Point v1, v2, v3;
    Vec3 objPos;
    int i;
    
    theWindow.setUndoRecord(new UndoRecord(theWindow, false, UndoRecord.COPY_OBJECT, new Object [] {theMesh, theMesh.duplicate()}));

    if (moveVert)
      {
	for (i = 0; i < vt.length; i++)
	  {
	    if (!visible[i])
	      continue;
	    v1 = screenVert[i];
	    if (pos.x < v1.x-HANDLE_SIZE/2 || pos.x > v1.x+HANDLE_SIZE/2 ||
		pos.y < v1.y-HANDLE_SIZE/2 || pos.y > v1.y+HANDLE_SIZE/2)
	      continue;
	    z = theCamera.getObjectToView().timesZ(vt[i].r);
	    if (z < closestz)
	      {
                result.index = i;
                result.type = ClickTarget.TYPE_VERTEX;
                result.clickPos = new Vec3(vt[i].r);
		closestz = z;
	      }
	  }
      }
    if (result.index == -1 && edgeSubdiv)
      {
	for (i = 0; i < ed.length; i++)
	  {
	    if (!visible[ed[i].v1] || !visible[ed[i].v2])
	      continue;
            if (theView.isEdgeHidden(i))
              continue;
	    v1 = screenVert[ed[i].v1];
	    v2 = screenVert[ed[i].v2];
	    if ((pos.x < v1.x-HANDLE_SIZE/2 && pos.x < v2.x-HANDLE_SIZE/2) ||
	    	(pos.x > v1.x+HANDLE_SIZE/2 && pos.x > v2.x+HANDLE_SIZE/2) ||
	    	(pos.y < v1.y-HANDLE_SIZE/2 && pos.y < v2.y-HANDLE_SIZE/2) ||
	    	(pos.y > v1.y+HANDLE_SIZE/2 && pos.y > v2.y+HANDLE_SIZE/2))
	      continue;

	    // Determine the distance of the click point from the line.

	    if (Math.abs(v1.x-v2.x) > Math.abs(v1.y-v2.y))
	      {
		if (v2.x > v1.x)
		  {
		    v = ((double) pos.x-v1.x)/(v2.x-v1.x);
		    u = 1.0-v;
		  }
		else
		  {
		    u = ((double) pos.x-v2.x)/(v1.x-v2.x);
		    v = 1.0-u;
		  }
		w = u*v1.y + v*v2.y - pos.y;
	      }
	    else
	      {
		if (v2.y > v1.y)
		  {
		    v = ((double) pos.y-v1.y)/(v2.y-v1.y);
		    u = 1.0-v;
		  }
		else
		  {
		    u = ((double) pos.y-v2.y)/(v1.y-v2.y);
		    v = 1.0-u;
		  }
		w = u*v1.x + v*v2.x - pos.x;
	      }
	    if (Math.abs(w) > HANDLE_SIZE/2)
	      continue;
            objPos = vt[ed[i].v1].r.times(u).plus(
                       vt[ed[i].v2].r.times(v));
            z = theCamera.getObjectToView().timesZ(objPos);
	    if (z < closestz)
	      {
                result.index = i;
                result.type = ClickTarget.TYPE_EDGE;
                result.clickPos = objPos;
                result.u = u;
                result.v = v;
		closestz = z;
	      }
	  }
      }
    if (result.index == -1 && faceSubdiv)
      {
	double e1x, e1y, e2x, e2y, denom, vx, vy;

	for (i = 0; i < fc.length; i++)
	  {
	    if (!visible[fc[i].v1] || !visible[fc[i].v2] || !visible[fc[i].v3])
	      continue;
	    v1 = screenVert[fc[i].v1];
	    v2 = screenVert[fc[i].v2];
	    v3 = screenVert[fc[i].v3];
	    if ((pos.x < v1.x-HANDLE_SIZE/2 && pos.x < v2.x-HANDLE_SIZE/2 && pos.x < v3.x-HANDLE_SIZE/2) ||
	    	(pos.x > v1.x+HANDLE_SIZE/2 && pos.x > v2.x+HANDLE_SIZE/2 && pos.x > v3.x+HANDLE_SIZE/2) ||
	    	(pos.y < v1.y-HANDLE_SIZE/2 && pos.y < v2.y-HANDLE_SIZE/2 && pos.y < v3.y-HANDLE_SIZE/2) ||
	    	(pos.y > v1.y+HANDLE_SIZE/2 && pos.y > v2.y+HANDLE_SIZE/2 && pos.y > v3.y+HANDLE_SIZE/2))
	      continue;

	    // Determine whether the click point was inside the triangle.

	    e1x = v1.x-v2.x;
	    e1y = v1.y-v2.y;
	    e2x = v1.x-v3.x;
	    e2y = v1.y-v3.y;
	    denom = 1.0/(e1x*e2y-e1y*e2x);
	    e1x *= denom;
	    e1y *= denom;
	    e2x *= denom;
	    e2y *= denom;
	    vx = pos.x - v1.x;
	    vy = pos.y - v1.y;
	    v = e2x*vy - e2y*vx;
	    if (v < 0.0 || v > 1.0)
	      continue;
	    w = vx*e1y - vy*e1x;
	    if (w < 0.0 || w > 1.0)
	      continue;
	    u = 1.0-v-w;
	    if (u < 0.0 || u > 1.0)
	      continue;
            objPos = vt[fc[i].v1].r.times(u).plus(
                       vt[fc[i].v2].r.times(v).plus(
                       vt[fc[i].v3].r.times(w)));
            z = theCamera.getObjectToView().timesZ(objPos);
	    if (z < closestz)
	      {
                result.index = i;
                result.type = ClickTarget.TYPE_FACE;
                result.clickPos = objPos;
                result.u = u;
                result.v = v;
		closestz = z;
	      }
	  }
      }
    return result;
  }

  /** Fake version of findSelectionDistance where only {@link #vertexToMove}
    is assumed to be selected.
  */

  int[] findSelectionDistance(ViewerCanvas viewcan)
  {
    TriMeshViewer view = (TriMeshViewer)viewcan;
    TriangleMesh mesh = (TriangleMesh)view.getObject().object;
    int i, j;
    int dist[] = new int [mesh.getVertices().length];
    TriangleMesh.Edge e[] = mesh.getEdges();
    TriangleMesh.Face f[] = mesh.getFaces();

    int maxDistance = TriMeshEditorWindow.getTensionDistance();

    for (i = 0; i < dist.length; i++)
      dist[i] = i == vertexToMove ? 0 : -1;

    // Now extend this outward up to maxDistance.

    for (i = 0; i < maxDistance; i++)
      for (j = 0; j < e.length; j++)
	{
          if (view.isEdgeHidden(j))
            continue;
	  if (dist[e[j].v1] == -1 && dist[e[j].v2] == i)
	    dist[e[j].v1] = i+1;
	  else if (dist[e[j].v2] == -1 && dist[e[j].v1] == i)
	    dist[e[j].v2] = i+1;
	}
    return dist;
  }

  public void mousePressed(WidgetMouseEvent e, ViewerCanvas view)
  {
    TriMeshViewer tmv = (TriMeshViewer) view;
    System.out.println("CreateVertexTool.mousePressed"+view.hashCode());
    TriangleMesh mesh = (TriangleMesh)tmv.getObject().object;
    Camera cam = tmv.getCamera();
    clickPoint = e.getPoint();
    
    target = findClickTarget(clickPoint, tmv);
    System.out.println("CreateVertexTool.mousePressed findClickTarget "+
      target.index);
    if (target.index == -1)
    {
      if (freeVert)
      {
        double distToScreen = cam.getDistToScreen();
        target.clickPos = cam.convertScreenToWorld(clickPoint, distToScreen);

        mesh.addVertex(new TriangleMesh.Vertex(target.clickPos));
        vertexToMove = mesh.getVertices().length-1;
      }
    }
    else
    {
      if (target.type == ClickTarget.TYPE_VERTEX)
      { // Begin moving vertex
        vertexToMove = target.index;
      }
      else
      { // Subdivide edge or face
        boolean[] split;
        TriangleMesh newmesh = null;
        if (target.type == ClickTarget.TYPE_EDGE)
        {  // Linear subdivide edge
          split = new boolean[mesh.getEdges().length];
          split[target.index] = true;

          newmesh = TriangleMesh.subdivideLinear(mesh, split, target.u);
        }
        else if (target.type == ClickTarget.TYPE_FACE)
        { // Linear subdivide face
          split = new boolean[mesh.getFaces().length];
          split[target.index] = true;

          newmesh = TriangleMesh.subdivideFaces(mesh, split,
              target.u, target.v);
        }
        vertexToMove = newmesh.getVertices().length-1;
        mesh.copyObject(newmesh);
        mesh.informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
        theWindow.updateMenus();
      }
    }
    dragged = false;     
  }
  
  public void mouseDragged(WidgetMouseEvent e, ViewerCanvas view)
  {
    Mesh mesh = (Mesh) ((MeshViewer) view).getObject().object;
    Vec3 vert[] = mesh.getVertexPositions();
    java.awt.Graphics g = view.getComponent().getGraphics();
    Camera cam = view.getCamera();
    Point dragPoint = e.getPoint();
    Vec3 v[], drag;
    Vec2 p;
    int i, dx, dy, size = MeshViewer.HANDLE_SIZE;
    int selectDist[] = findSelectionDistance(view);

    if (dragged)
      view.drawImage(g);
    dragged = true;
    dx = dragPoint.x - clickPoint.x;
    dy = dragPoint.y - clickPoint.y;
    if (e.isShiftDown())
      {
	if (Math.abs(dx) > Math.abs(dy))
	  dy = 0;
	else
	  dx = 0;
      }
    v = ReshapeMeshTool.findDraggedPositions(target.clickPos, vert, dx, dy,
        (MeshViewer) view, e.isControlDown(), selectDist);
    if (e.isControlDown())
      drag = view.getCamera().getCameraCoordinates().getZDirection().times(-dy*0.01);
    else
      drag = view.getCamera().findDragVector(target.clickPos, dx, dy);
    g.setColor(java.awt.Color.gray);
    ((MeshViewer) view).drawDraggedSelection(g, cam, v, true);
    theWindow.setHelpText(Translate.text("reshapeMeshTool.dragText",
        Math.round(drag.x*1e5)/1e5+", "+Math.round(drag.y*1e5)/1e5+", "+Math.round(drag.z*1e5)/1e5));
    g.dispose();
  }

  public void mouseReleased(WidgetMouseEvent e, ViewerCanvas view)
  {
    Object3D meshobj = ((MeshViewer) view).getObject().object;
    Mesh mesh = (Mesh) meshobj;
    Vec3 vert[] = mesh.getVertexPositions();
    Camera cam = view.getCamera();
    Point dragPoint = e.getPoint();
    int i, dx, dy;
    Vec3 v[], drag;
    int selectDist[] = findSelectionDistance(view);

    dx = dragPoint.x - clickPoint.x;
    dy = dragPoint.y - clickPoint.y;
    if (e.isShiftDown())
      {
	if (Math.abs(dx) > Math.abs(dy))
	  dy = 0;
	else
	  dx = 0;
      }
    if (dx != 0 || dy != 0)
      {
//??	theWindow.setUndoRecord(new UndoRecord(theWindow, false, UndoRecord.COPY_OBJECT, new Object [] {mesh, mesh.duplicate()}));
//	v = findDraggedPositions(target.clickPos, vert, dx, dy,
//            (MeshViewer) view, e.isControlDown());
        v = ReshapeMeshTool.findDraggedPositions(target.clickPos, vert, dx, dy,
            (MeshViewer) view, e.isControlDown(), selectDist);
	mesh.setVertexPositions(v);
      }
    meshobj.informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
    theWindow.setHelpText(Translate.text("reshapeMeshTool.helpText"));
  }

}