/*
Violet - A program for editing UML diagrams.

Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)

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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package com.horstmann.violet;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import com.horstmann.violet.framework.Direction;
import com.horstmann.violet.framework.Edge;
import com.horstmann.violet.framework.Graph;
import com.horstmann.violet.framework.Grid;
import com.horstmann.violet.framework.Node;
import com.horstmann.violet.framework.RectangularNode;

/**
   A method call node in a scenario diagram.
*/
public class CallNode extends RectangularNode
{
   /**
      Construct a call node with a default size
   */
   public CallNode()
   {
      setBounds(new Rectangle2D.Double(0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT));
   }

   public void draw(Graphics2D g2)
   {
      super.draw(g2);
      Color oldColor = g2.getColor();
      g2.setColor(Color.WHITE);
      g2.fill(getBounds());
      g2.setColor(oldColor);
      if (openBottom)
      {
         Rectangle2D b = getBounds();
         double x1 = b.getX();
         double x2 = x1 + b.getWidth();
         double y1 = b.getY();
         double y3 = y1 + b.getHeight();
         double y2 = y3 - CALL_YGAP;
         g2.draw(new Line2D.Double(x1, y1, x2, y1));
         g2.draw(new Line2D.Double(x1, y1, x1, y2));
         g2.draw(new Line2D.Double(x2, y1, x2, y2));
         Stroke oldStroke = g2.getStroke();
         g2.setStroke(new BasicStroke(1.0f, 
                         BasicStroke.CAP_ROUND, 
                         BasicStroke.JOIN_ROUND, 
                         0.0f, 
                         new float[] { 5.0f, 5.0f }, 0.0f));
         g2.draw(new Line2D.Double(x1, y2, x1, y3));
         g2.draw(new Line2D.Double(x2, y2, x2, y3));
         g2.setStroke(oldStroke);
      }
      else
         g2.draw(getBounds());
   }

   /**
      Gets the implicit parameter of this call.
      @return the implicit parameter node
   */
   public ImplicitParameterNode getImplicitParameter()
   {
      return implicitParameter;
   }

   /**
      Sets the implicit parameter of this call.
      @param newValue the implicit parameter node
   */
   public void setImplicitParameter(ImplicitParameterNode newValue)
   {
      implicitParameter = newValue;
   }

   public Point2D getConnectionPoint(Direction d)
   {
      if (d.getX() > 0)
         return new Point2D.Double(getBounds().getMaxX(),
            getBounds().getMinY());
      else
         return new Point2D.Double(getBounds().getX(),
            getBounds().getMinY());
   }

   public boolean addEdge(Edge e, Point2D p1, Point2D p2)
   {
      Node end = e.getEnd();
      if (end == null) return false;

      if (e instanceof ReturnEdge) // check that there is a matching call 
         return end == getParent();
         
      if (!(e instanceof CallEdge)) return false;
      
      Node n = null;
      if (end instanceof CallNode) 
      {
         // check for cycles
         Node parentNode = this; 
         while (parentNode != null && end != parentNode)
            parentNode = parentNode.getParent();
         
         if (end.getParent() == null && end != parentNode)
         {
            n = end;
         }
         else
         {
            CallNode c = new CallNode();
            c.implicitParameter = ((CallNode)end).implicitParameter;
            e.connect(this, c);
            n = c;
         }
      }
      else if (end instanceof ImplicitParameterNode)
      {
         if (((ImplicitParameterNode)end).getTopRectangle().contains(p2))
         {
            n = end;
            ((CallEdge)e).setMiddleLabel("\u00ABcreate\u00BB");
         }
         else
         {
            CallNode c = new CallNode();
            c.implicitParameter = (ImplicitParameterNode)end;
            e.connect(this, c);
            n = c;
         }
      }
      else return false;

      int i = 0;
      List calls = getChildren();
      while (i < calls.size() && ((Node)calls.get(i)).getBounds().getY() <= p1.getY()) 
         i++;
      addChild(i, n);
      return true;
   }

   public void removeEdge(Graph g, Edge e)
   {
      if (e.getStart() == this)
         removeChild(e.getEnd());
   }

   public void removeNode(Graph g, Node n)
   {
      if (n == getParent() || n == implicitParameter)
         g.removeNode(this);
   }
   
   private static Edge findEdge(Graph g, Node start, Node end)
   {
      Collection edges = g.getEdges();
      Iterator iter = edges.iterator(); 
      while (iter.hasNext())
      {
         Edge e = (Edge) iter.next();
         if (e.getStart() == start && e.getEnd() == end) return e;
      }
      return null;
   }

   public void layout(Graph g, Graphics2D g2, Grid grid)
   {
      
      if (implicitParameter == null) return;
      double xmid = implicitParameter.getBounds().getCenterX();

      for (CallNode c = (CallNode)getParent(); 
           c != null; c = (CallNode)c.getParent())
         if (c.implicitParameter == implicitParameter)
            xmid += getBounds().getWidth() / 2;

      translate(xmid - getBounds().getCenterX(), 0);
      double ytop = getBounds().getY() + CALL_YGAP;

      List calls = getChildren();
      for (int i = 0; i < calls.size(); i++)
      {
         Node n = (Node) calls.get(i);
         if (n instanceof ImplicitParameterNode) // <<create>>
         {
            n.translate(0, ytop - ((ImplicitParameterNode) n).getTopRectangle().getCenterY());
            ytop += ((ImplicitParameterNode)n).getTopRectangle().getHeight() / 2 + CALL_YGAP;
         }
         else if (n instanceof CallNode)
         {  
            Edge callEdge = findEdge(g, this, n);
            // compute height of call edge
            if (callEdge != null)
            {
               Rectangle2D edgeBounds = callEdge.getBounds(g2);
               ytop += edgeBounds.getHeight() - CALL_YGAP;
            }
            
            n.translate(0, ytop - n.getBounds().getY());
            n.layout(g, g2, grid);
            if (((CallNode) n).signaled)
               ytop += CALL_YGAP;
            else
               ytop += n.getBounds().getHeight() + CALL_YGAP;
         }
      }
      if (openBottom) ytop += 2 * CALL_YGAP;
      Rectangle2D b = getBounds();
      
      double minHeight = DEFAULT_HEIGHT;
      Edge returnEdge = findEdge(g, this, getParent());
      if (returnEdge != null)
      {
         Rectangle2D edgeBounds = returnEdge.getBounds(g2);
         minHeight = Math.max(minHeight, edgeBounds.getHeight());         
      }
      setBounds(new Rectangle2D.Double(b.getX(), b.getY(), b.getWidth(), 
            Math.max(minHeight, ytop - b.getY())));
   }

   public boolean addNode(Node n, Point2D p)
   {
      return n instanceof PointNode;
   }

   /**
      Sets the signaled property.
      @param newValue true if this node is the target of a signal edge
   */      
   public void setSignaled(boolean newValue) { signaled = newValue; }

   /**
      Gets the openBottom property.
      @return true if this node is the target of a signal edge
   */
   public boolean isOpenBottom() { return openBottom; }

   /**
      Sets the openBottom property.
      @param newValue true if this node is the target of a signal edge
   */      
   public void setOpenBottom(boolean newValue) { openBottom = newValue; }


   private ImplicitParameterNode implicitParameter;
   private boolean signaled;
   private boolean openBottom;
   
   private static int DEFAULT_WIDTH = 16;
   private static int DEFAULT_HEIGHT = 30;
   public static int CALL_YGAP = 20;
}
