/*
 * TouchGraph LLC. Apache-Style Software License
 *
 *
 * Copyright (c) 2001-2002 Alexander Shapiro. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by 
 *        TouchGraph LLC (http://www.touchgraph.com/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "TouchGraph" or "TouchGraph LLC" must not be used to endorse 
 *    or promote products derived from this software without prior written 
 *    permission.  For written permission, please contact 
 *    alex@touchgraph.com
 *
 * 5. Products derived from this software may not be called "TouchGraph",
 *    nor may "TouchGraph" appear in their name, without prior written
 *    permission of alex@touchgraph.com.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL TOUCHGRAPH OR ITS CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 */

package com.touchgraph.graphlayout;

import  java.awt.Color;
import  java.awt.Dimension;
import  java.awt.Point;
import  java.awt.Font;
import  java.awt.FontMetrics;
import  java.awt.Graphics;
import  java.util.Date;
import  java.util.Vector;
import  java.util.Iterator;

/**  Node.
  *
  *  @author   Alexander Shapiro
  *  @author   Murray Altheim (2001-11-06; added support for round rects and alternate Node colors)
  *  @version  1.21  $Id: Node.java,v 1.1.1.1 2007/01/07 07:58:19 andrew Exp $
  */
public class Node {
    
   /** This Node's type is a Rectangle. */
    public final static int TYPE_RECTANGLE = 1;

   /** This Node's type is a Round Rectangle. */
    public final static int TYPE_ROUNDRECT = 2;

   /** This Node's type is an Ellipse. */
    public final static int TYPE_ELLIPSE   = 3;

   /** This Node's type is a Circle. */
    public final static int TYPE_CIRCLE    = 4;

    public static final Font SMALL_TAG_FONT = new Font("Courier",Font.PLAIN,9);

   // Variables that store default values for colors + fonts + node type
    public static Color BACK_FIXED_COLOR        = Color.red;
    public static Color BACK_SELECT_COLOR       = new Color(255, 224, 0);
    public static Color BACK_DEFAULT_COLOR      = new Color(208, 96, 0);
    public static Color BACK_HILIGHT_COLOR      = Color.decode("#ffb200"); // altheim: new
    
    public static Color BORDER_DRAG_COLOR       = Color.black;
    public static Color BORDER_MOUSE_OVER_COLOR = new Color(160,160,160);
    public static Color BORDER_INACTIVE_COLOR   = Color.white;

    public static Color TEXT_COLOR              = Color.white;
    
    public static Font TEXT_FONT = new Font("Courier",Font.PLAIN,12);
    
    public static int DEFAULT_TYPE = 1;
    
   /** an int indicating the Node type. 
     * @see TYPE_RECTANGLE
     * @see TYPE_ROUNDRECT
     * @see TYPE_ELLIPSE
     */
    protected int typ = TYPE_RECTANGLE;
    private String id;

    public double drawx;
    public double drawy;

    protected FontMetrics fontMetrics;
    protected Font font;

    protected String lbl;
    protected Color backColor = BACK_DEFAULT_COLOR;
    protected Color textColor = TEXT_COLOR;
        
    public double x;
    public double y;

    protected double dx; //Used by layout
    protected double dy; //Used by layout

    protected boolean fixed;
    protected int repulsion; //Used by layout

    public boolean justMadeLocal = false;
    public boolean markedForRemoval = false;
    
    public int visibleEdgeCnt; //Should only be modified by graphelements.VisibleLocality
    protected boolean visible;

    private Vector edges;


  // ............

   /** Minimal constructor which will generate an ID value from Java's Date class.
     * Defaults will be used for type and color. The label will be taken from the ID value.
     */
    public Node()
    {
        initialize(null);
        lbl = id;
    }

   /** Constructor with the required ID <tt>id</tt>, using defaults 
     * for type (rectangle), color (a static variable from TGPanel).
     * The Node's label will be taken from the ID value.
     */
    public Node( String id )
    {
        initialize(id);
        lbl = id;
    }

   /** Constructor with Strings for ID <tt>id</tt> and <tt>label</tt>, using defaults 
     * for type (rectangle) and color (a static variable from TGPanel).
     * If the label is null, it will be taken from the ID value.
     */
    public Node( String id, String label )
    {
        initialize(id);
        if ( label == null ) lbl = id;
        else lbl = label;
    }

   /** Constructor with a String ID <tt>id</tt>, an int <tt>type</tt>, Background Color <tt>bgColor</tt>, 
     * and a String <tt>label</tt>. If the label is null, it will be taken from the ID value.
     * @see TYPE_RECTANGLE
     * @see TYPE_ROUNDRECT
     */
    public Node( String id, int type, Color color, String label )
    {
        initialize(id);
        typ = type;
        backColor = color;
        if ( label == null ) lbl = id;
        else lbl = label;
    }

    private void initialize( String identifier ) {
        this.id = identifier;
        edges = new Vector();
        x = Math.random()*2-1; // If multiple nodes are added without repositioning,
        y = Math.random()*2-1; // randomizing starting location causes them to spread out nicely.
        repulsion = 100;
        font = TEXT_FONT;
        fixed = false;
        typ = DEFAULT_TYPE;
        visibleEdgeCnt=0;
        visible = false;
    }


   // setters and getters ...............
   
    public static void setNodeBackFixedColor( Color color ) { BACK_FIXED_COLOR = color; }
    public static void setNodeBackSelectColor( Color color ) { BACK_SELECT_COLOR = color; }
    public static void setNodeBackDefaultColor( Color color ) { BACK_DEFAULT_COLOR = color; }
    public static void setNodeBackHilightColor( Color color ) { BACK_HILIGHT_COLOR = color; }
    public static void setNodeBorderDragColor( Color color ) { BORDER_DRAG_COLOR = color; }
    public static void setNodeBorderMouseOverColor( Color color ) { BORDER_MOUSE_OVER_COLOR = color; }
    public static void setNodeBorderInactiveColor( Color color ) { BORDER_INACTIVE_COLOR = color; }
    public static void setNodeTextColor( Color color ) { TEXT_COLOR = color; }
    public static void setNodeTextFont( Font font ) { TEXT_FONT = font; }
    public static void setNodeType( int type ) { DEFAULT_TYPE = type; }
  
    /** Set the ID of this Node to the String <tt>id</tt>.
      */
    public void setID( String id ) {
        this.id = id;
    }

    /** Return the ID of this Node as a String.
     */
    public String getID() {
        return id;
    }

    /** Set the location of this Node provided the Point <tt>p</tt>.
      */
    public void setLocation( Point p ) {
        this.x = p.x;
        this.y = p.y;
    }
 
 
    /** Return the location of this Node as a Point.
      */
    public Point getLocation() {
        return new Point((int)x,(int)y);
    }

    /** Set the visibility of this Node to the boolean <tt>v</tt>. 
      */
    public void setVisible( boolean v) {
        visible = v;
    } 

    /** Return the visibility of this Node as a boolean.
      */
    public boolean isVisible() {
        return visible;
    } 
 
    /** Set the type of this Node to the int <tt>type</tt>.
      * @see TYPE_RECTANGLE
      * @see TYPE_ROUNDRECT 
      * @see TYPE_ELLIPSE
      * @see TYPE_CIRCLE
      */
      
    public void setType( int type ) {
        typ = type;
    }

    /** Return the type of this Node as an int.
      * @see TYPE_RECTANGLE
      * @see TYPE_ROUNDRECT 
      * @see TYPE_ELLIPSE
      * @see TYPE_CIRCLE
      */
    public int getType() {
        return typ;
    }
 
    /** Set the font of this Node to the Font <tt>font</tt>. */
    public void setFont( Font font ) {
        this.font = font;
    }

    /** Returns the font of this Node as a Font*/
    public Font getFont() {
        return font;
    }
  
    /** Set the background color of this Node to the Color <tt>bgColor</tt>. */
    public void setBackColor( Color bgColor ) {
        backColor = bgColor;
    }

   /** Return the background color of this Node as a Color.
     */
    public Color getBackColor() {
        return backColor;
    }

    /** Set the text color of this Node to the Color <tt>txtColor</tt>. */
    public void setTextColor( Color txtColor ) {
        textColor = txtColor;
    }
   
    
   /** Return the text color of this Node as a Color.
     */
    public Color getTextColor() {
        return textColor;
    }


   /** Set the label of this Node to the String <tt>label</tt>. */
    public void setLabel( String label ) {
        lbl = label;
    }

   /** Return the label of this Node as a String.
     */
    public String getLabel() {
        return lbl;
    }

   /** Set the fixed status of this Node to the boolean <tt>fixed</tt>. */
    public void setFixed( boolean fixed ) {
        this.fixed = fixed;
    }
 
 
   /** Returns true if this Node is fixed (in place).
     */
    public boolean getFixed() {
        return fixed;
    }
 
    // ....

    /** Return the number of Edges in the cumulative Vector. 
      * @deprecated        this method has been replaced by the <tt>edgeCount()</tt> method.
      */
    public int edgeNum() {
        return edges.size(); 
    }

    /** Return the number of Edges in the cumulative Vector. */
    public int edgeCount() {
        return edges.size(); 
    }

    /** Return an iterator over the Edges in the cumulative Vector, null if it is empty. */
    public Iterator getEdges() {
        if ( edges.size() == 0 ) return null;
        else return edges.iterator(); 
    }

    /** Returns the local Edge count. */
    public int visibleEdgeCount() {
        return visibleEdgeCnt;
    }

    /** Return the Edge at int <tt>index</tt>. */
    public Edge edgeAt( int index ) {
        return (Edge)edges.elementAt(index);
    }

    /** Add the Edge <tt>edge</tt> to the graph. */
    public void addEdge( Edge edge ) {
        if ( edge == null ) return;
        edges.addElement(edge);
    }

    /** Remove the Edge <tt>edge</tt> from the graph. */
    public void removeEdge( Edge edge ) {
        edges.removeElement(edge);
    }

    /** Return the width of this Node. */
    public int getWidth() {
        if ( fontMetrics != null && lbl != null ) {
            return fontMetrics.stringWidth(lbl) + 12;            
        } else {
            return 10;
        }
    }

    /** Return the height of this Node. */
    public int getHeight() {
        if ( fontMetrics != null ) {
            return fontMetrics.getHeight() + 6;
        } else {
            return 6;
        }
    }

    /** Returns true if this Node intersects Dimension <tt>d</tt>. */
    public boolean intersects( Dimension d ) {
        return ( drawx > 0 && drawx < d.width && drawy>0 && drawy < d.height );
    }

    /** Returns true if this Node contains the Point <tt>px,py</tt>. */
    public boolean containsPoint( double px, double py ) {
        return (( px > drawx-getWidth()/2) && ( px < drawx+getWidth()/2) 
                && ( py > drawy-getHeight()/2) && ( py < drawy+getHeight()/2));
    }

    /** Returns true if this Node contains the Point <tt>p</tt>. */
    public boolean containsPoint( Point p ) {
        return (( p.x > drawx-getWidth()/2) && ( p.x < drawx+getWidth()/2) 
                && ( p.y > drawy-getHeight()/2) && ( p.y < drawy+getHeight()/2));
    }

    /** Paints the Node. */
    public void paint( Graphics g, TGPanel tgPanel ) {
        if (!intersects(tgPanel.getSize()) ) return;
        paintNodeBody(g, tgPanel);
            
        if ( visibleEdgeCount()<edgeCount() ) {
            int ix = (int)drawx;
            int iy = (int)drawy;
            int h = getHeight();
            int w = getWidth();
            int tagX = ix+(w-7)/2-2+w%2;
            int tagY = iy-h/2-2;
            char character;
            int hiddenEdgeCount = edgeCount()-visibleEdgeCount();
            character = (hiddenEdgeCount<9) ? (char) ('0' + hiddenEdgeCount) : '*';
            paintSmallTag(g, tgPanel, tagX, tagY, Color.red, Color.white, character);
        }
    }

    public Color getPaintBorderColor(TGPanel tgPanel) {
        if (this == tgPanel.getDragNode()) return BORDER_DRAG_COLOR;
        else if (this == tgPanel.getMouseOverN()) return BORDER_MOUSE_OVER_COLOR;
        else return BORDER_INACTIVE_COLOR;
    }

    public Color getPaintBackColor(TGPanel tgPanel) {
        if ( this == tgPanel.getSelect() ) {
            return BACK_SELECT_COLOR;
        } else {
            if (fixed) return BACK_FIXED_COLOR;
            if (markedForRemoval) return new Color(100,60,40);
            if (justMadeLocal) return new Color(255,220,200);
            return backColor;            
        }
    }        
    
    public Color getPaintTextColor(TGPanel tgPanel) {
        return textColor;
    }

    /** Paints the background of the node, along with its label */
    public void paintNodeBody( Graphics g, TGPanel tgPanel) {
        g.setFont(font);
        fontMetrics = g.getFontMetrics();
        
        int ix = (int)drawx;
        int iy = (int)drawy;
        int h = getHeight();
        int w = getWidth();
        int r = h/2+1; // arc radius

        Color borderCol = getPaintBorderColor(tgPanel);
        g.setColor(borderCol);

        if ( typ == TYPE_ROUNDRECT ) {             
            g.fillRoundRect(ix - w/2, iy - h / 2, w, h, r, r);
        } else if ( typ == TYPE_ELLIPSE ) {
            g.fillOval(ix - w/2, iy - h / 2, w, h );
        } else if ( typ == TYPE_CIRCLE ) { // just use width for both dimensions
            g.fillOval(ix - w/2, iy - w / 2, w, w );
        } else { // TYPE_RECTANGLE
            g.fillRect(ix - w/2, iy - h / 2, w, h);
        }

        Color backCol = getPaintBackColor(tgPanel);
        g.setColor(backCol);

        if ( typ == TYPE_ROUNDRECT ) {
            g.fillRoundRect(ix - w/2+2, iy - h / 2+2, w-4, h-4, r, r );
        } else if ( typ == TYPE_ELLIPSE ) {
            g.fillOval(ix - w/2+2, iy - h / 2+2, w-4, h-4 );
        } else if ( typ == TYPE_CIRCLE ) {
            g.fillOval(ix - w/2+2, iy - w / 2+2, w-4, w-4 );
        } else { // TYPE_RECTANGLE
            g.fillRect(ix - w/2+2, iy - h / 2+2, w-4, h-4);
        }

        Color textCol = getPaintTextColor(tgPanel);
        g.setColor(textCol);
        g.drawString(lbl, ix - fontMetrics.stringWidth(lbl)/2, iy + fontMetrics.getDescent() +1);
    }

    /** Paints a tag with containing a character in a small font. */
    public void paintSmallTag(Graphics g, TGPanel tgPanel, int tagX, int tagY, 
                              Color backCol, Color textCol, char character) {
        g.setColor(backCol);
        g.fillRect(tagX, tagY, 8, 8);
        g.setColor(textCol);
        g.setFont(SMALL_TAG_FONT);
        g.drawString(""+character, tagX+2, tagY+7);
    }

} // end com.touchgraph.graphlayout.Node
