/*
 * Licensed Materials - Property of Rogue Wave Software, Inc. 
 * © Copyright Rogue Wave Software, Inc. 2014, 2017 
 * © Copyright IBM Corp. 2009, 2014
 * © Copyright ILOG 1996, 2009
 * All Rights Reserved.
 *
 * Note to U.S. Government Users Restricted Rights:
 * The Software and Documentation were developed at private expense and
 * are "Commercial Items" as that term is defined at 48 CFR 2.101,
 * consisting of "Commercial Computer Software" and
 * "Commercial Computer Software Documentation", as such terms are
 * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
 * as applicable.
 */

package xmlgrapher;

import java.awt.Color;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import ilog.views.IlvGrapher;
import ilog.views.IlvGraphic;
import ilog.views.IlvHandlesSelection;
import ilog.views.IlvLinkImage;
import ilog.views.IlvManagerView;
import ilog.views.IlvRect;
import ilog.views.IlvStroke;
import ilog.views.IlvTransformer;
import ilog.views.graphic.IlvIcon;
import ilog.views.graphic.IlvPolylineLinkImage;

/**
 * A view that can display an XML file
 * describing a network. This is a simplified version that reads the
 * data from an XML file describing the locations using x/y coordinates.
 */
public class XmlGrapher extends IlvManagerView
{
  // Indexes for Layers
  final static int MAP_LAYER = 0;
  final static int LINK_LAYER = 1;
  final static int NODE_LAYER = 2;

  // URL of XML file loaded
  private URL _network;

  // Size of node defined in XML file
  private double _nodeSize = 35;

  // Size of font defined in XML file
  private int _fontSize = 12;

  // URL of the map 
  private String _mapurl;
  
  public static boolean verbose = false;

  /**
   *  Creates an XmlGrapher.
   */
  public XmlGrapher()
  {
    super(new IlvGrapher());

    setAntialiasing(true);
    setKeepingAspectRatio(true);
    setBackground(Color.white);

    // Settings parameters for selection handles
    IlvHandlesSelection.defaultHandleColor = Color.black;
    IlvHandlesSelection.defaultHandleBackgroundColor = Color.white;
    IlvHandlesSelection.defaultHandleShape = IlvHandlesSelection.SQUARE_SHAPE;
  }

  /**
   * Returns the URL of the XML file currenlty loaded.
   */
  public URL getNetwork()
  {
    return _network;
  }

  /**
   * Reads the XML network file.
   * This method reads the XML file and populate 
   * the grapher. 
   */
  public void setNetwork(URL url)
  {
    this._network = url;

    verbose ("setting Network " + _network);

    getManager().setContentsAdjusting(true);

    try {
      // Deletes everything in the grapher.
      getManager().deleteAll(false);
      
      for (int i = 0 ; i < getManager().getLayersCount(); i++)
         getManager().removeLayer(0, false); 

      // Creates 3 layers, Map, Nodes and Links
      getManager().setNumberOfLayer(3);
 
      try {
        verbose ("Reading XML file");
        
        //parse XML file

        Document doc = 
          DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(url.toString());

        Element root = doc.getDocumentElement();
        
        // Alpha (transparency) for the map layer

        float mapAlpha = (float) getDoubleAttribute(root, "map_alpha", 1.0f);
        getManager().getManagerLayer(MAP_LAYER).setAlpha(mapAlpha);
        
        // URL of the map file

        _mapurl = root.getAttribute("map");
        
        if (_mapurl != null && _mapurl.length() != 0) {
          try {
            setMap(new URL(_network, _mapurl));
          } catch (MalformedURLException mue) {
            setMap(null);
          }
        }
        else 
          setMap(null);
        
        // size of nodes and size of font
        _nodeSize = getDoubleAttribute(root, "nodeSize", 35.f);
        _fontSize = (int)getDoubleAttribute(root, "fontSize", 8);
        
        
        // read <node> objects
        for (Node node = root.getFirstChild(); 
             node != null ;
             node = node.getNextSibling()) {
          if (node.getNodeName().equals("node")) {
            addNode((Element)node);
          }
        }
        // read <link> objects
        for (Node node = root.getFirstChild(); 
             node != null ;
             node = node.getNextSibling()) {
          if (node.getNodeName().equals("link")) {
            addLink((Element)node);
          }
        }
        verbose ("Nodes added");
      } catch (Exception e) {
        e.printStackTrace();
      }
      verbose ("Layout Done");

      setTransformer(new IlvTransformer());

      getManager().getManagerLayer(MAP_LAYER).setName("background Map");
      getManager().getManagerLayer(LINK_LAYER).setName("Links");
      getManager().getManagerLayer(NODE_LAYER).setName("Cities");
            
      if (getParent()!=null) 
        repaint();

    } finally {
      getManager().setContentsAdjusting(false);
      verbose ("setNetwork Done");
    }
  }

  /**
   * Creates the graphic link
   * based on the XML link element.
   * @param linkElement the XML link element
   */
  private void addLink(Element linkElement)
  {
    Element origin      = getOrigin(linkElement);
    Element destination = getDestination(linkElement);

    if (origin == null || destination == null)
      throw new IllegalArgumentException("no origin or dest");
    
    // Look for origin and destination nodes
    String idf = origin.getAttribute("idref");
    String idt = destination.getAttribute("idref");

    IlvGraphic fromg = getManager().getObject(idf);
    IlvGraphic tog   = getManager().getObject(idt);

    if (fromg == null)
      throw new IllegalArgumentException("bad ids " + idf);
    
    if (tog == null)
      throw new IllegalArgumentException("bad ids " + idt);
      
    // Create the link
    IlvLinkImage link = new IlvPolylineLinkImage(fromg, tog, false, null);
    // Sets the link style
    setLinkStyle(link, getElement(linkElement, "style"));
    // Sets the end-cap option
    link.setEndCap(IlvStroke.CAP_BUTT);
    // Adds the link
    ((IlvGrapher)getManager()).addLink(link, LINK_LAYER, false);
  }

  /**
   * Creates the graphic object based
   * on the XML node element.
   * @param nodeElement the XML node element
   */
  private void addNode(Element nodeElement)
  {

    String label     = nodeElement.getAttribute("label");
    String positionX = getLocation(nodeElement).getAttribute("x");
    String positionY  = getLocation(nodeElement).getAttribute("y");
    String id        = nodeElement.getAttribute("ID");
    
    double x = Double.valueOf(positionX).doubleValue();
    double y = Double.valueOf(positionY).doubleValue();
    
    IlvRect r = new IlvRect(x, y, _nodeSize, _nodeSize);
        
    // Creates the node
    GrapherNode obj = new GrapherNode(r, label);
    obj.setFontSize(_fontSize);
    obj.setBackground(Color.lightGray);
    ((IlvGrapher)getManager()).addNode(obj, NODE_LAYER, false);
    obj.setName(id);
  }


  /**
   * Sets the style of the specified link based on the
   * style element.
   */
  private static void setLinkStyle(IlvLinkImage link, Element style)
  {
    link.setLineWidth(1);
    link.setMaximumLineWidth(1);

    if (style == null)
      return;
    String fill = style.getAttribute("fill");
    if (fill != null) link.setForeground(stringToColor(fill));
    String width = style.getAttribute("width");
    if (width != null) {
      try {
        double w = Double.parseDouble(width);
        if (w <=0) w = 1;
        link.setLineWidth(w);
        link.setMaximumLineWidth(w);
      } catch (NumberFormatException e) {
      }
    } 
  }

  /**
   * Sets the map.
   */
  public void setMap(URL url)
  {
    getManager().deleteAll(MAP_LAYER, true);
    if (url == null) 
      return;

    // Loads the image
    Image img = Toolkit.getDefaultToolkit().getImage(url);
    MediaTracker tracker = new MediaTracker(this);
    tracker.addImage(img, 0);
    try {
      tracker.waitForID(0);
    } catch (InterruptedException e) {
      return;
    }

    // Creates the IlvIcon
    IlvIcon icon = new IlvIcon(img,
                               new IlvRect(0, 0,
                                           img.getWidth(null),
                                           img.getHeight(null)));
    getManager().addObject(icon, MAP_LAYER, true);
    getManager().setMovable(icon, false);
    getManager().setSelectable(icon, false);
    getManager().setEditable(icon, false);
  }
  
  /**
   * Returns the location element from a node element.
   */
  private static Element getLocation(Element node)
  {
    return getElement(node, "location");
  }
  
  /**
   * Returns the origin element from a link element
   */
  private static Element getOrigin(Element node)
  {
    return getElement(node, "origin");
  }

  /**
   * Returns the destination element from a link element
   */
  private static Element getDestination(Element node)
  {
    return getElement(node, "destination");
  }

  /**
   * Returns the child element of the specified node element with
   * the specified tag name.
   */
  private static Element getElement(Element node, String tagName)
  {
    for (Node n = node.getFirstChild(); 
         n != null ;
         n = n.getNextSibling()) 
      if (n.getNodeName().equals(tagName))
        return (Element)n;
    return null;
  }

  /**
   * Converts an Color expressed as #RRGGBB to Awt Color.
   */
  private static Color stringToColor(String color)
  {
    if (color == null)
      return Color.black;
    color.trim();
    if (color.startsWith("#")) {
      try {
        color = color.substring(1);
        if (color.length() == 6) {
          int v = Integer.decode("0x"+color).intValue();
          return new Color((v >> 32) & 0xFF, (v >> 8) & 0xFF, (v ) & 0xFF);
        }
        return Color.black;
      } catch (NumberFormatException e) {
        verbose("bad color");
        return Color.black;
      }
    } else
      return Color.black;
  }

  /**
   * Gets an attribute name 'att' expressed in String 
   * and convert it to double.
   */
  private static double getDoubleAttribute(Element node, String att, double def)
  {
    String s = node.getAttribute(att);
    double d = def;
    if (s == null || s.length() ==0)
       return d;
    try {
      d = Double.parseDouble(s);
    } catch (NumberFormatException e) {
    }
    return d;
  }

  private static void verbose(String msg)
  {
    if (verbose)
      System.out.println(msg);
  } 
}