/* Copyright (C) 1999-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.ui.*;
import buoy.event.*;
import buoy.widget.*;
import java.awt.*;

/** The CurveViewer class is a component which displays a Curve object and 
    allows the user to edit it. */

public class CurveViewer extends MeshViewer
{
  boolean draggingSelectionBox, dragging;

  public CurveViewer(ObjectInfo obj, RowContainer p)
  {
    super(obj, p);
  }

  protected void drawObject(Graphics g)
  {
    if (!showMesh)
      return;
    MeshVertex v[] = ((Mesh) theObject).getVertices();
    boolean[] selected = getSelection();

    if (renderMode == RENDER_WIREFRAME)
      Object3D.draw(g, theCamera, objInfo.getWireframePreview(), objInfo.getBounds());
    else
      {
        // Calculate the screen coordinates of every vertex.
    
        WireframeMesh wireframe = theObject.getWireframeMesh();
        for (int i = 0; i < wireframe.from.length; i++)
          renderLine(wireframe.vert[wireframe.from[i]], wireframe.vert[wireframe.to[i]], theCamera, Color.black);
      }
    for (int i = 0; i < v.length; i++)
      if (!selected[i] && theCamera.getObjectToView().timesZ(v[i].r) > theCamera.getClipDistance())
	{
          Vec2 p = theCamera.getObjectToScreen().timesXY(v[i].r);
          if (renderMode == RENDER_WIREFRAME)
            g.fillRect(((int) p.x) - HANDLE_SIZE/2, ((int) p.y) - HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE);
          else
            {
              double z = theCamera.getObjectToView().timesZ(v[i].r);
              renderBox(((int) p.x) - HANDLE_SIZE/2, ((int) p.y) - HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE, z, Color.black);
            }
	}
    Color col = (currentTool.hilightSelection() ? Color.magenta : Color.black);
    if (g != null)
      g.setColor(col);
    for (int i = 0; i < v.length; i++)
      if (selected[i] && theCamera.getObjectToView().timesZ(v[i].r) > theCamera.getClipDistance())
	{
          Vec2 p = theCamera.getObjectToScreen().timesXY(v[i].r);
          if (renderMode == RENDER_WIREFRAME)
            g.fillRect(((int) p.x) - HANDLE_SIZE/2, ((int) p.y) - HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE);
          else
            {
              double z = theCamera.getObjectToView().timesZ(v[i].r);
              renderBox(((int) p.x) - HANDLE_SIZE/2, ((int) p.y) - HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE, z, col);
            }
	}
  }
  
  /* Draw any parts of the mesh which have been dragged. */
  
  public void drawDraggedSelection(Graphics g, Camera cam, Vec3 v[], boolean broadcast)
  {
    MeshVertex vert[] = ((Curve) theObject).getVertices();
    int i;

    for (i = 1; i < vert.length; i++)
      {
        cam.drawClippedLine(g, v[i-1], v[i]);
        Vec2 p = cam.getObjectToScreen().timesXY(v[i]);
	g.fillRect(((int) p.x) - HANDLE_SIZE/2, ((int) p.y) - HANDLE_SIZE/2, HANDLE_SIZE, HANDLE_SIZE);
      }
    if (((Curve) theObject).isClosed())
      cam.drawClippedLine(g, v[i-1], v[0]);
    
    broadcastDraggedSelection(v, broadcast);
  }
  
  public void setSelection(boolean sel[])
  {
    getSelectionHolder().setSelection(sel);
    findSelectionDistance();
    currentTool.getWindow().updateMenus();
    updateImage();
    repaint();
  }

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

  protected void findSelectionDistance()
  {
    Curve theCurve = (Curve) theObject;
    int i, j, dist[] = new int [theCurve.getVertices().length];
    boolean[] selected = getSelection();
    
    maxDistance = CurveEditorWindow.getTensionDistance();
    
    // First, set each distance to 0 or -1, depending on whether that vertex is part of the
    // current selection.
    
    for (i = 0; i < dist.length; i++)
      dist[i] = selected[i] ? 0 : -1;

    // Now extend this outward up to maxDistance.

    for (i = 0; i < maxDistance; i++)
      {
	for (j = 0; j < dist.length-1; j++)
	  if (dist[j] == -1 && dist[j+1] == i)
	    dist[j] = i+1;
	for (j = 1; j < dist.length; j++)
	  if (dist[j] == -1 && dist[j-1] == i)
	    dist[j] = i+1;
	if (theCurve.isClosed())
	  {
	    if (dist[0] == -1 && dist[dist.length-1] == i)
	      dist[0] = i+1;
	    if (dist[0] == i && dist[dist.length-1] == -1)
	      dist[dist.length-1] = i+1;
	  }
      }
    selectionDistance = dist;
  }
  
  public void setMesh(Mesh mesh)
  {
    Curve obj = (Curve) mesh;
    setObject(obj);
//    boolean[] selected = getSelection();
//
//    if (selected.length != obj.getVertices().length)
//      selected = new boolean [obj.getVertices().length];
    getSelectionHolder().setSelection(
        Utilities.arrayEnsureLength(getSelection(), obj.getVertices().length));
    findSelectionDistance();
    currentTool.getWindow().updateMenus();
  }

  /* When the user presses the mouse, forward events to the current tool as appropriate.
     If this is a vertex based tool, allow them to select or deselect vertices. */

  protected void mousePressed(WidgetMouseEvent e)
  {
    int i, j, x, y;
    double z, nearest;
    MeshVertex v[] = ((Curve) theObject).getVertices();
    boolean[] selected = getSelection();
    Vec2 pos;
    Point p;

    requestFocus();
    sentClick = true;
    deselect = -1;
    dragging = false;
    clickPoint = e.getPoint();
    
    // Determine which tool is active.
    
    if (metaTool != null && e.isMetaDown())
      activeTool = metaTool;
    else if (altTool != null && e.isAltDown())
      activeTool = altTool;
    else
      activeTool = currentTool;

    // If the current tool wants all clicks, just forward the event and return.

    if (activeTool.whichClicks() == EditingTool.ALL_CLICKS)
      {
	activeTool.mousePressed(e, this);
	dragging = true;
	return;
      }
    
    // See whether the click was on a currently selected vertex.
    
    p = e.getPoint();
    for (i = 0; i < v.length; i++)
      if (selected[i])
	{
	  pos = theCamera.getObjectToScreen().timesXY(v[i].r);
	  x = (int) pos.x;
	  y = (int) pos.y;
	  if (x >= p.x-HANDLE_SIZE/2 && x <= p.x+HANDLE_SIZE/2 && y >= p.y-HANDLE_SIZE/2 && y <= p.y+HANDLE_SIZE/2)
	    break;
	}
    if (i < v.length)
      {
	// The click was on a selected vertex.  If it was a shift-click, the user may want
	// to deselect it, so set a flag.  Forward the event to the current tool.
	
	if (e.isShiftDown())
	  deselect = i;
	activeTool.mousePressedOnHandle(e, this, 0, i);
	return;
      }

    // The click was not on a selected vertex.  See whether it was on an unselected one.
    // If so, select it and send an event to the current tool.
    
    j = -1;
    nearest = Double.MAX_VALUE;
    for (i = 0; i < v.length; i++)
      {
	pos = theCamera.getObjectToScreen().timesXY(v[i].r);
	x = (int) pos.x;
	y = (int) pos.y;
	if (x >= p.x-HANDLE_SIZE/2 && x <= p.x+HANDLE_SIZE/2 && y >= p.y-HANDLE_SIZE/2 && y <= p.y+HANDLE_SIZE/2)
	  {
	    z = theCamera.getObjectToView().timesZ(v[i].r);
	    if (z > 0.0 && z < nearest)
	      {
		nearest = z;
		j = i;
	      }
	  }
      }
    if (j > -1)
      {
	if (!e.isShiftDown())
	  for (i = 0; i < selected.length; i++)
	    selected[i] = false;
	selected[j] = true;
        getSelectionHolder().informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
	findSelectionDistance();
	activeTool.getWindow().updateMenus();
	if (e.isShiftDown())
	  {
	    sentClick = false;
	    updateImage();
	    repaint();
	  }
	else
	  activeTool.mousePressedOnHandle(e, this, 0, j);
	return;
      }
    
    // The click was not on a handle.  Start dragging a selection box.
    
    draggingSelectionBox = true;
    beginDraggingSelection(p, false);
    sentClick = false;
  }

  protected void mouseDragged(WidgetMouseEvent e)
  {
    if (!dragging)
      {
	Point p = e.getPoint();
	if (Math.abs(p.x-clickPoint.x) < 2 && Math.abs(p.y-clickPoint.y) < 2)
	  return;
      }
    dragging = true;
    deselect = -1;
    super.mouseDragged(e);
  }

  protected void mouseReleased(WidgetMouseEvent e)
  {
    Rectangle r;
    int i, j, x, y;
    MeshVertex v[] = ((Curve) theObject).getVertices();
    boolean[] selected = getSelection();
    Vec2 pos;

    moveToGrid(e);
    endDraggingSelection();
    if (draggingSelectionBox && !e.isShiftDown() && !e.isControlDown())
      for (i = 0; i < selected.length; i++)
        selected[i] = false;

    // If the user was dragging a selection box, then select or deselect anything 
    // it intersects.
    
    if (selectBounds != null)
      {
	boolean newsel = !e.isControlDown();
	for (i = 0; i < v.length; i++)
	  {
	    pos = theCamera.getObjectToScreen().timesXY(v[i].r);
	    x = (int) pos.x;
	    y = (int) pos.y;
	    if (selectionRegionContains(new Point(x, y)))
	      selected[i] = newsel;
	  }
      }
    draggingBox = draggingSelectionBox = false;

    // Send the event to the current tool, if appropriate.

    if (sentClick)
      {
	if (!dragging)
	  {
	    Point p = e.getPoint();
	    e.translatePoint(clickPoint.x-p.x, clickPoint.y-p.y);
	  }
	activeTool.mouseReleased(e, this);
      }

    // If the user shift-clicked a selected point and released the mouse without dragging,
    // then deselect the point.

    if (deselect > -1)
      selected[deselect] = false;
    getSelectionHolder().informChanged(this, ModelEvent.CHANGEDUR_OBJECTEDITOR);
    findSelectionDistance();
    activeTool.getWindow().updateMenus();
    //updateImage();
    //repaint();
  }
    
}
