/*
 *    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.structure.opt;

import java.util.ArrayList;
import java.util.Iterator;

import org.geotools.graph.structure.DirectedEdge;
import org.geotools.graph.structure.DirectedGraphable;
import org.geotools.graph.structure.DirectedNode;
import org.geotools.graph.structure.Edge;
import org.geotools.graph.structure.Graphable;
import org.geotools.graph.structure.Node;

/**
 * Optimized implementation of DirectedEdge.
 * 
 * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net
 *
 * @see DirectedEdge
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/graph/src/main/java/org/geotools/graph/structure/opt/OptDirectedEdge.java $
 */
public class OptDirectedEdge extends OptGraphable implements DirectedEdge {

  /** in node **/
  private OptDirectedNode m_in;
  
  /** out node **/
  private OptDirectedNode m_out;
  
  /** 
   * Constructs a new OptDirectedEdge.
   * 
   * @param in Optimized in node.
   * @param out Optimized out node.
   */
  public OptDirectedEdge(OptDirectedNode in, OptDirectedNode out) {
    m_in = in;
    m_out = out;  
  }
  
  /**
   * @see DirectedEdge#getInNode()
   */
  public DirectedNode getInNode() {
    return(m_in);
  }

  /**
   * @see DirectedEdge#getOutNode()
   */
  public DirectedNode getOutNode() {
    return(m_out);
  }

  /**
   * @see Edge#getNodeA()
   */
  public Node getNodeA() {
    return(m_in);
  }

  /**
   * @see Edge#getNodeB()
   */
  public Node getNodeB() {
    return(m_out);
  }

  /**
   * @see Edge#getOtherNode(Node)
   */
  public Node getOtherNode(Node node) {
    return(node == m_in ? m_out : node == m_out ? m_in : null); 
  }

  /**
   * Unsupported Operation.
   * 
   * @throws UnsupportedOperationException
   */
  public void reverse() {
    throw new UnsupportedOperationException(
      getClass().getName() + "#reverse()"   
    );
  }

  /**
   * @see Edge#compareNodes(Edge)
   */
  public int compareNodes(Edge other) {
    if (m_in.equals(other.getNodeA()) && m_out.equals(other.getNodeB()))
      return(Edge.EQUAL_NODE_ORIENTATION);
      
    if (m_in.equals(other.getNodeB()) && m_out.equals(other.getNodeA())) 
      return(Edge.OPPOSITE_NODE_ORIENTATION);
      
    return(Edge.UNEQUAL_NODE_ORIENTATION);
  }

  /**
   * @see Graphable#getRelated()
   */
  public Iterator getRelated() {
    ArrayList related = new ArrayList(
      m_in.getDegree() + m_out.getDegree() - 2 
    );
    
    Edge[] edges = m_in.getInEdgeArray();
    for (int i = 0; i < edges.length; i++) {
      related.add(edges[i]);  
    }
    
    edges = m_in.getOutEdgeArray();
    for (int i = 0; i < edges.length; i++) {
      Edge e = edges[i]; 
      if (!e.equals(this) && !(e.getNodeA().equals(e.getNodeB()))) 
        related.add(edges[i]);  
    }
    
    edges = m_out.getInEdgeArray();
    for (int i = 0; i < edges.length; i++) {
      Edge e = edges[i];
      
      switch(compareNodes(e)) {
        case Edge.EQUAL_NODE_ORIENTATION:
        case Edge.OPPOSITE_NODE_ORIENTATION:
          continue; //already added
        
        case Edge.UNEQUAL_NODE_ORIENTATION:
          related.add(e);
      }  
    }
    
    edges = m_out.getOutEdgeArray();
    for (int i = 0; i < edges.length; i++) {
      Edge e = edges[i];
      
      switch(compareNodes(edges[i])) {
        case Edge.EQUAL_NODE_ORIENTATION:
        case Edge.OPPOSITE_NODE_ORIENTATION:
          continue; //already added
        
        case Edge.UNEQUAL_NODE_ORIENTATION:
          if (!e.getNodeA().equals(e.getNodeB())) related.add(e);
      }  
    }
    
    return(related.iterator());
  }
  
  /**
   * @see DirectedGraphable#getInRelated()
   */
  public Iterator getInRelated() {
    return(new RelatedIterator(RelatedIterator.IN));  
  }

  /**
   * @see DirectedGraphable#getOutRelated()
   */
  public Iterator getOutRelated() {
    return(new RelatedIterator(RelatedIterator.OUT));
  }
  
  /**
   * Iterator used to iterate over adjacent edges.
   * 
   * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net
   *
   */
  public class RelatedIterator implements Iterator {
    /** in mode **/
    public static final int IN = 0;
    
    /** out mode **/
    public static final int OUT = 1;
    
    /** both mode **/
    public static final int BOTH = 2;
    
    /** iteration mode **/
    private int m_mode;
    
    /** iteration index **/
    private int m_index;
    
    /** number of edges to iterate over **/
    private int m_n;
    
    /**
     * Constructs a new iterator.
     * 
     * @param mode Iteration mode.
     */
    public RelatedIterator(int mode) {
      m_mode = mode;
      m_index = 0;
      
      switch(m_mode) {
        case IN: 
          m_n = m_in.getInDegree();
          break;
          
        case OUT: 
          m_n = m_out.getOutDegree();
          break;
        
        default:
          m_n = 0;       
      }
    }
    
    /**
     * Unsupported Operation.
     * 
     * @throws UnsupportedOperationException
     */
    public void remove() {
      throw new UnsupportedOperationException(
        getClass().getName() + "#remove()"
      );
    }

    /**
     * Determines if there are any more related edges to return.
     * 
     * @see Iterator#hasNext()
     */
    public boolean hasNext() {
      return(m_index < m_n);  
    }

    /**
     * Returns the next related edge.
     * 
     * @see Iterator#next()
     */
    public Object next() {
      switch(m_mode) {
        case IN: return(m_in.getInEdgeArray()[m_index++]);
        case OUT: return(m_out.getOutEdgeArray()[m_index++]);
      }
      
      return(null);
    }
  }
}
