/*
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.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

import com.horstmann.violet.framework.SerializableEnumeration;

/**
   A style for a segmented line that indicates the number
   and sequence of bends.
*/
public class BentStyle extends SerializableEnumeration
{
   private BentStyle() {}
  
   /**
      Gets the points at which a line joining two rectangles
      is bent according to this bent style.
      @param start the starting rectangle
      @param end the ending rectangle
      @return an array list of points at which to bend the
      segmented line joining the two rectangles
   */
   public ArrayList getPath(Rectangle2D start, Rectangle2D end)
   {
      ArrayList r = getPath(this, start, end);
      if (r != null) return r;
      
      if (start.equals(end)) r = getSelfPath(start);
      else if (this == HVH) r = getPath(VHV, start, end);
      else if (this == VHV) r = getPath(HVH, start, end);
      else if (this == HV) r = getPath(VH, start, end);
      else if (this == VH) r = getPath(HV, start, end);
      if (r != null) return r;

      return getPath(STRAIGHT, start, end);
   }

   /**
      Gets the four connecting points at which a bent line
      connects to a rectangle.
   */
   private static Point2D[] connectionPoints(Rectangle2D r)
   {
      Point2D[] a = new Point2D[4];
      a[0] = new Point2D.Double(r.getX(), r.getCenterY());
      a[1] = new Point2D.Double(r.getMaxX(), r.getCenterY());
      a[2] = new Point2D.Double(r.getCenterX(), r.getY());
      a[3] = new Point2D.Double(r.getCenterX(), r.getMaxY());
      return a;
   }
   
   /**
      Gets the points at which a line joining two rectangles
      is bent according to a bent style.
      @param start the starting rectangle
      @param end the ending rectangle
      @return an array list of points at which to bend the
      segmented line joining the two rectangles
   */
   private static ArrayList getPath(BentStyle bent, 
      Rectangle2D s, Rectangle2D e)
   {
      ArrayList r = new ArrayList();
      if (bent == STRAIGHT)
      {
         Point2D[] a = connectionPoints(s);
         Point2D[] b = connectionPoints(e);
         Point2D p = a[0];
         Point2D q = b[0];
         double distance = p.distance(q);
         if (distance == 0) return null;
         for (int i = 0; i < a.length; i++)
            for (int j = 0; j < b.length; j++)
            {
               double d = a[i].distance(b[j]);
               if (d < distance)
               {
                  p = a[i]; q = b[j];
                  distance = d;
               }
            }
         r.add(p);
         r.add(q);
      }
      else if (bent == HV)
      {
         double x1;
         double x2 = e.getCenterX();
         double y1 = s.getCenterY();
         double y2;
         if (x2 + MIN_SEGMENT <= s.getX())
            x1 = s.getX();
         else if (x2 - MIN_SEGMENT >= s.getMaxX())
            x1 = s.getMaxX();
         else return null;
         if (y1 + MIN_SEGMENT <= e.getY())
            y2 = e.getY();
         else if (y1 - MIN_SEGMENT >= e.getMaxY())
            y2 = e.getMaxY();
         else return null;
         r.add(new Point2D.Double(x1, y1));
         r.add(new Point2D.Double(x2, y1));
         r.add(new Point2D.Double(x2, y2));
      }
      else if (bent == VH)
      {
         double x1 = s.getCenterX();
         double x2;
         double y1;
         double y2 = e.getCenterY();
         if (x1 + MIN_SEGMENT <= e.getX())
            x2 = e.getX();
         else if (x1 - MIN_SEGMENT >= e.getMaxX())
            x2 = e.getMaxX();
         else return null;
         if (y2 + MIN_SEGMENT <= s.getY())
            y1 = s.getY();
         else if (y2 - MIN_SEGMENT >= s.getMaxY())
            y1 = s.getMaxY();
         else return null;
         r.add(new Point2D.Double(x1, y1));
         r.add(new Point2D.Double(x1, y2));
         r.add(new Point2D.Double(x2, y2));
      }
      else if (bent == HVH)
      {
         double x1;
         double x2;
         double y1 = s.getCenterY();
         double y2 = e.getCenterY();
         if (s.getMaxX() + 2 * MIN_SEGMENT <= e.getX())
         {
            x1 = s.getMaxX();
            x2 = e.getX();
         }
         else if (e.getMaxX() + 2 * MIN_SEGMENT <= s.getX())
         {
            x1 = s.getX();
            x2 = e.getMaxX();
         }
         else return null;
         if (Math.abs(y1 - y2) <= MIN_SEGMENT)
         {
            r.add(new Point2D.Double(x1, y2));
            r.add(new Point2D.Double(x2, y2));
         }
         else
         {
            r.add(new Point2D.Double(x1, y1));
            r.add(new Point2D.Double((x1 + x2) / 2, y1));
            r.add(new Point2D.Double((x1 + x2) / 2, y2));
            r.add(new Point2D.Double(x2, y2));
         }
      }
      else if (bent == VHV)
      {
         double x1 = s.getCenterX();
         double x2 = e.getCenterX();
         double y1;
         double y2;
         if (s.getMaxY() + 2 * MIN_SEGMENT <= e.getY())
         {
            y1 = s.getMaxY();
            y2 = e.getY();
         }
         else if (e.getMaxY() + 2 * MIN_SEGMENT <= s.getY())
         {
            y1 = s.getY();
            y2 = e.getMaxY();

         }
         else return null;
         if (Math.abs(x1 - x2) <= MIN_SEGMENT)
         {
            r.add(new Point2D.Double(x2, y1));
            r.add(new Point2D.Double(x2, y2));
         }
         else
         {
            r.add(new Point2D.Double(x1, y1));
            r.add(new Point2D.Double(x1, (y1 + y2) / 2));
            r.add(new Point2D.Double(x2, (y1 + y2) / 2));
            r.add(new Point2D.Double(x2, y2));
         }
      }
      else return null;
      return r;
   }

   /**
      Gets the points at which a line joining two rectangles
      is bent according to a bent style.
      @param s the starting and ending rectangle
   */
   private static ArrayList getSelfPath(Rectangle2D s)
   {
      ArrayList r = new ArrayList();
      double x1 = s.getX() + s.getWidth() * 3 / 4;
      double y1 = s.getY();
      double y2 = s.getY() - SELF_HEIGHT;
      double x2 = s.getX() + s.getWidth() + SELF_WIDTH;
      double y3 = s.getY() + s.getHeight() / 4;
      double x3 = s.getX() + s.getWidth();
      r.add(new Point2D.Double(x1, y1));
      r.add(new Point2D.Double(x1, y2));
      r.add(new Point2D.Double(x2, y2));
      r.add(new Point2D.Double(x2, y3));
      r.add(new Point2D.Double(x3, y3));
      return r;
   }

   private static final int MIN_SEGMENT = 10;
   private static final int SELF_WIDTH = 30;
   private static final int SELF_HEIGHT = 25;

   public static final BentStyle STRAIGHT = new BentStyle();
   public static final BentStyle HV = new BentStyle();
   public static final BentStyle VH = new BentStyle();
   public static final BentStyle HVH = new BentStyle();
   public static final BentStyle VHV = new BentStyle();
}
