/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002-2006, GeoTools Project Managment Committee (PMC)
 *    (C) 2002, Refractions Reserach Inc.
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package org.geotools.graph.build.line;

import java.util.HashMap;
import java.util.Map;

import org.geotools.graph.build.GraphBuilder;
import org.geotools.graph.build.GraphGenerator;
import org.geotools.graph.build.basic.BasicGraphBuilder;
import org.geotools.graph.structure.Edge;
import org.geotools.graph.structure.Graph;
import org.geotools.graph.structure.Graphable;
import org.geotools.graph.structure.Node;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineSegment;

/**
 * An implementation of GraphGenerator used to generate a graph representing a
 * line network. Graphs are generated by supplying the generator with objects
 * of type LineSegment via the add(Object) method. <BR>
 * <BR>
 * For each line segment added, an edge in the graph is created. The builder
 * records the end coordinates of each line added, and maintains a map of 
 * coordinates to nodes, creating nodes when neccessary.<BR>
 * <BR>
 * Edges created by the generator are of type BasicEdge and contain an object
 * of type LineSegment.<BR>
 * Nodes created by the generator are of type BasicXYNode and contain an object
 * of type Coordinate. 
 *
 * @see org.geotools.graph.structure.line.BasicXYNode
 * @see org.geotools.graph.structure.basic.BasicEdge
 * @see com.vividsolutions.jts.geom.LineSegment
 * @see com.vividsolutions.jts.geom.Coordinate
 * 
 * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net
 *
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/graph/src/main/java/org/geotools/graph/build/line/BasicLineGraphGenerator.java $
 */
public class BasicLineGraphGenerator implements LineGraphGenerator {

  /** coordinate to node map **/
  private HashMap m_coord2node;
  
  /** underlying builder **/
  private GraphBuilder m_builder;
  
  /**
   * Constructs a new BasicLineGraphGenerator.
   */
  public BasicLineGraphGenerator () {
    m_coord2node = new HashMap();
    setGraphBuilder(new BasicGraphBuilder());  
  }
  
  /**
   * Adds a line to the graph. 
   * 
   * @param obj An instance of LineSegment.
   * 
   * @return A BasicEdge.
   * 
   * @see LineSegment
   * @see GraphGenerator#add(Object)
   */
  public Graphable add(Object obj) {
    LineSegment line = (LineSegment)obj;
    Coordinate c; 
    Node n1, n2;
    
    //check first coordinate
    c = line.p0;
    if ((n1 = (Node)m_coord2node.get(c)) == null) {
      //first time coordinate seen, create node for it
      n1 = getGraphBuilder().buildNode();
      
      //set underlying object to coordinate 
      //n1.setObject(c);
      setObject(n1, c);
      
      getGraphBuilder().addNode(n1);
      m_coord2node.put(c,n1);
    }
    
    //check second coordinate
    c = line.p1;
    if ((n2 = (Node)m_coord2node.get(c)) == null) {
      //first time coordinate seen, create node for it
      n2 = getGraphBuilder().buildNode();
      
      //set underlying object to coordiante 
      //n2.setObject(c);
      setObject(n2,c);
      
      getGraphBuilder().addNode(n2);
      m_coord2node.put(c,n2); 
    }
    
    //build the edge setting underlying object to line
    Edge e = getGraphBuilder().buildEdge(n1,n2);
    //e.setObject(line);
    setObject(e, line);
    
    getGraphBuilder().addEdge(e);
    
    //return the created edge
    return(e);  
  }

  /**
   * Returns the edge which represents a line. Note that if the exact same line 
   * has been added to the graph multiple times, then only one of the edges that
   * represents it will be returned. It is undefined which edge will be returned.
   * 
   * @param obj An instance of LineSegment.
   * 
   * @return Edge that represents the line.
   * 
   * @see GraphGenerator#get(Object)
   */
  public Graphable get(Object obj) {
    LineSegment line = (LineSegment)obj;
    
    //get nodes representing coordinate
    Node n1 = (Node)m_coord2node.get(line.p0);
    Node n2 = (Node)m_coord2node.get(line.p1);
    
    if (n1 == null || n2 == null) return(null);
    
    //return edge shared between them
    return(n1.getEdge(n2));
    
    //note: if there are identical lines in the graph then it is undefined
    //which of them will be returned
  }

  /**
   * Removes the edge from the graph that represents a line.
   *
   * @return Edge that represents the line.
   *
   * @see GraphGenerator#remove(Object) 
   */ 
  public Graphable remove(Object obj) {
    LineSegment line = (LineSegment)obj;
    Node n1 = (Node)m_coord2node.get(line.p0);
    Node n2 = (Node)m_coord2node.get(line.p1);
    
    if (n1 == null || n2 == null) return(null);
    
    Edge e = (Edge)n1.getEdge(n2);
    getGraphBuilder().removeEdge(e);
    
    return(e);
  }

  /**
   * @see GraphGenerator#setGraphBuilder(GraphBuilder)
   */
  public void setGraphBuilder(GraphBuilder builder) {
    m_builder = builder;
  }

  /**
   * @see GraphGenerator#getGraphBuilder()
   */
  public GraphBuilder getGraphBuilder() {
    return(m_builder);
  }
  
  /**
   * @see GraphGenerator#getGraph()
   */
  public Graph getGraph() {
    return(getGraphBuilder().getGraph());
  }

  /**
   * Returns the coordinate to node map used to build nodes representing line
   * endpoint coordinates.
   * 
   * @return coordinate to ndoe map.
   */
  public Map getNodeMap() {
    return(m_coord2node);  
  }

  //TODO COMMENT ME!
  public Node getNode(Coordinate c) {
    return((Node)m_coord2node.get(c));  
  }

  public Edge getEdge(Coordinate c1, Coordinate c2) {
    Node n1 = (Node)m_coord2node.get(c1);
    Node n2 = (Node)m_coord2node.get(c2);
    
    return(n1.getEdge(n2));  
  }
  
  protected void setObject(Edge e, Object obj) {
    e.setObject(obj);
  }
  
  protected void setObject(Node n, Object obj) {
    n.setObject(obj);
  }  
}
