/**
 * Container for link between two Osm nodes
 *
 * @author ab
 */
package btools.mapaccess;

import java.util.ArrayList;
import java.util.HashMap;

import btools.util.ByteArrayUnifier;
import btools.util.SortedHeap;

public final class OsmNodesMap
{
  private HashMap<OsmNode,OsmNode> hmap = new HashMap<OsmNode,OsmNode>(4096);
  
  private ByteArrayUnifier abUnifier = new ByteArrayUnifier( 16384, false );

  private OsmNode testKey = new OsmNode();
  
  public int nodesCreated;
  public long maxmem;  
  private long currentmaxmem = 4000000; // start with 4 MB
  public int lastVisitID = 1000;
  public int baseID = 1000;
  
  public OsmNode destination;
  public int currentPathCost;
  public int currentMaxCost = 1000000000;
  
  public OsmNode endNode1;
  public OsmNode endNode2;
  
  public int cleanupMode = 0;
  
  public void cleanupAndCount( OsmNode[] nodes )
  { 
    if ( cleanupMode == 0 )
    {
      justCount( nodes );
    }
    else
    {
      cleanupPeninsulas( nodes );
    }
  }

  private void justCount( OsmNode[] nodes )
  {  
    for( int i=0; i<nodes.length; i++ )
    { 
      OsmNode n = nodes[i];
      if ( n.firstlink != null )
      {
        nodesCreated++;
      }
    }
  }

  private void cleanupPeninsulas( OsmNode[] nodes )
  {  
    baseID = lastVisitID++;
    for( int i=0; i<nodes.length; i++ ) // loop over nodes again just for housekeeping
    { 
      OsmNode n = nodes[i];
      if ( n.firstlink != null )
      {
        if ( n.visitID == 1 )
        {
          try
          {
            minVisitIdInSubtree( null, n );
          }
          catch( StackOverflowError soe )
          {
             // System.out.println( "+++++++++++++++ StackOverflowError ++++++++++++++++" );
          }
        }
      }
    }
  }
  
  private int minVisitIdInSubtree( OsmNode source, OsmNode n )
  { 
      if ( n.visitID == 1 ) n.visitID = baseID; // border node
      else n.visitID = lastVisitID++;
      int minId = n.visitID;
      nodesCreated++;

      OsmLink nextLink = null;
      for( OsmLink l = n.firstlink; l != null; l = nextLink )
      {
        nextLink = l.getNext( n );
      
        OsmNode t = l.getTarget( n );
        if ( t == source ) continue;
        if ( t.isHollow() ) continue;
        
        int minIdSub = t.visitID; 
        if ( minIdSub == 1 )
        {
          minIdSub = baseID;
        }
        else if ( minIdSub == 0 )
        {
          int nodesCreatedUntilHere = nodesCreated;
          minIdSub = minVisitIdInSubtree( n, t);
          if ( minIdSub > n.visitID ) // peninsula ?
          {
            nodesCreated = nodesCreatedUntilHere;
            n.unlinkLink( l );
            t.unlinkLink( l );
          }
        }
        else if ( minIdSub < baseID )
        {
          continue;
        }
        else if ( cleanupMode == 2 )
        {
          minIdSub = baseID; // in tree-mode, hitting anything is like a gateway          
        }
        if ( minIdSub < minId ) minId = minIdSub;
      }
    return minId;
  }
      

  
  public boolean isInMemoryBounds( int npaths, boolean extend )
  {
//    long total = nodesCreated * 76L + linksCreated * 48L;
    long total = nodesCreated * 95L + npaths * 200L;
    
    if ( extend )
    {
      total += 100000;
    
      // when extending, try to have 1 MB  space
      long delta = total + 1900000 - currentmaxmem;
      if ( delta > 0 )
      {
        currentmaxmem += delta;
        if ( currentmaxmem > maxmem )
        {
          currentmaxmem = maxmem;
        }
      }
    }
    return total <= currentmaxmem;
  }

  private void addActiveNode( ArrayList<OsmNode> nodes2check, OsmNode n )
  {
    n.visitID = lastVisitID;
    nodesCreated++;
    nodes2check.add( n );
  }

  // is there an escape from this node
  // to a hollow node (or destination node) ?  
  public boolean canEscape( OsmNode n0 )
  {
    boolean sawLowIDs = false;
    lastVisitID++;
    nodes2check.clear();
    nodes2check.add( n0 );
    while ( !nodes2check.isEmpty() )
    {
      OsmNode n = nodes2check.remove( nodes2check.size()-1 );
      if ( n.visitID < baseID )
      {
        n.visitID = lastVisitID;
        nodesCreated++;
        for( OsmLink l = n.firstlink; l != null; l = l.getNext( n ) )
        {
          OsmNode t = l.getTarget( n );
          nodes2check.add( t );
        }
      }
      else if ( n.visitID < lastVisitID )
      {
        sawLowIDs = true;
      }
    }
    if ( sawLowIDs )
    {
      return true;
    }

    nodes2check.add( n0 );
    while ( !nodes2check.isEmpty() )
    {
      OsmNode n = nodes2check.remove( nodes2check.size()-1 );
      if ( n.visitID == lastVisitID )
      {
        n.visitID = lastVisitID;
        nodesCreated--;
        for( OsmLink l = n.firstlink; l != null; l = l.getNext( n ) )
        {
          OsmNode t = l.getTarget( n );
          nodes2check.add( t );
        }
        n.vanish();
      }
    }

    return false;
  }

  private  ArrayList<OsmNode> nodes2check;
  
  public void clearTemp()
  {
    nodes2check = null;
  }

  public void collectOutreachers()
  {
    nodes2check = new ArrayList<OsmNode>(nodesCreated);
    nodesCreated=0;
    for( OsmNode n : hmap.values() )
    {
      addActiveNode( nodes2check, n );
    }
    
    lastVisitID++;
    baseID = lastVisitID;

    while ( !nodes2check.isEmpty() )
    {
      OsmNode n = nodes2check.remove( nodes2check.size()-1 );
      n.visitID = lastVisitID;
      
      for( OsmLink l = n.firstlink; l != null; l = l.getNext( n ) )
      {
        OsmNode t = l.getTarget( n );
        if ( t.visitID != lastVisitID )
        {
          addActiveNode( nodes2check, t );
        }
      }
      if ( destination != null && currentMaxCost < 1000000000 )
      {
        int distance = n.calcDistance( destination );
        if ( distance > currentMaxCost - currentPathCost + 100 )
        {
          n.vanish();
        }
      }
      if ( n.firstlink == null )
      {
        nodesCreated--;
      }
    }
  }
  

  public ByteArrayUnifier getByteArrayUnifier()
  {
    return abUnifier;
  }
  
  /**
   * Get a node from the map
   * @return the node for the given id if exist, else null
   */
  public OsmNode get( int ilon, int ilat )
  {
    testKey.ilon = ilon;
    testKey.ilat = ilat;
    return hmap.get( testKey );
  }


  public void remove( OsmNode node )
  {
    if ( node != endNode1 && node != endNode2 ) // keep endnodes in hollow-map even when loaded
    {                                           // (needed for escape analysis)
      hmap.remove( node );
    }
  }

  /**
   * Put a node into the map
   * @return the previous node if that id existed, else null
   */
  public OsmNode put( OsmNode node )
  {
    return hmap.put( node, node );
  }

  // ********************** test cleanup **********************

  private static void addLinks( OsmNode[] nodes, int idx, boolean isBorder, int[] links )
  {
    OsmNode n = nodes[idx];
    n.visitID = isBorder ? 1 : 0;
    n.selev = (short)idx;
    for( int i : links )
    {
      OsmNode t = nodes[i];
      OsmLink link = n.isLinkUnused() ? n : ( t.isLinkUnused() ? t : null );
      if ( link == null )
      {
        link = new OsmLink();
      }
      n.addLink( link, false, t );
    }
  }

  public static void main( String[] args )
  {
    OsmNode[] nodes = new OsmNode[12];
    for( int i=0; i<nodes.length; i++ )
    {
      nodes[i]= new OsmNode( (i+1000)*1000,(i+1000)*1000 );
      
    }
    
    addLinks( nodes,  0, true , new int[]{1,5}   );  // 0
    addLinks( nodes,  1, true , new int[]{}      );  // 1
    addLinks( nodes,  2, false, new int[]{3,4}   );  // 2
    addLinks( nodes,  3, false, new int[]{4}     );  // 3
    addLinks( nodes,  4, false, new int[]{}      );  // 4
    addLinks( nodes,  5, true , new int[]{6,9}   );  // 5
    addLinks( nodes,  6, false, new int[]{7,8}   );  // 6
    addLinks( nodes,  7, false, new int[]{}      );  // 7
    addLinks( nodes,  8, false, new int[]{}      );  // 8
    addLinks( nodes,  9, false, new int[]{10,11} );  // 9
    addLinks( nodes, 10, false, new int[]{11}    );  // 10
    addLinks( nodes, 11, false, new int[]{}      );  // 11
    
    OsmNodesMap nm = new OsmNodesMap();
    
    nm.cleanupMode = 2;
    
    nm.cleanupAndCount( nodes );
    
    System.out.println( "nodesCreated=" + nm.nodesCreated );
    nm.cleanupAndCount( nodes );
    
    System.out.println( "nodesCreated=" + nm.nodesCreated );
    
  }
  
}
