/*
 * 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  com.touchgraph.graphlayout.graphelements.*;

import  java.util.*;
 
/** LocalityUtils:  Utilities for switching locality.  Animation effects
  * require a reference to TGPanel.
  *     
  * @author   Alexander Shapiro                                        
  * @version  1.21  $Id: LocalityUtils.java,v 1.1.1.1 2007/01/07 07:58:19 andrew Exp $
  */
public class LocalityUtils {

    TGPanel tgPanel;
    Locality locality;
    
    public static final int INFINITE_LOCALITY_RADIUS = Integer.MAX_VALUE;
    
    ShiftLocaleThread shiftLocaleThread;
    boolean fastFinishShift=false;  // If finish fast is true, quickly wrap up animation
    
    public LocalityUtils(Locality loc, TGPanel tgp) {
        locality = loc;
        tgPanel = tgp;
    }

    public void fastFinishAnimation() {
        fastFinishShift = true;
    }

    /** Mark for deletion nodes not contained within distHash. */
    private synchronized boolean markDistantNodes(final Collection subgraph) {
        final boolean[] someNodeWasMarked = new boolean[1]; 
        someNodeWasMarked[0] = false;
        Boolean x;
        TGForEachNode fen = new TGForEachNode() {
            public void forEachNode(Node n) {
                if(!subgraph.contains(n)) { 
                    n.markedForRemoval=true;
                    someNodeWasMarked[0] = true;
                }
            }
        };
        
        locality.forAllNodes(fen);
        return someNodeWasMarked[0];
    }

    private synchronized void removeMarkedNodes() {
        final Vector nodesToRemove = new Vector();
        
        TGForEachNode fen = new TGForEachNode() {
            public void forEachNode(Node n) {
                if(n.markedForRemoval)  {
                    nodesToRemove.addElement(n);
                    n.markedForRemoval=false;    
                }
            }    
        };
        synchronized(locality) {    
            locality.forAllNodes(fen);
            locality.removeNodes(nodesToRemove);
        }    
    }

    /** Add to locale nodes within radius distance of a focal node. */
    private synchronized void addNearNodes(Hashtable distHash, int radius) throws TGException {
        for ( int r=0; r<radius+1; r++ ) {
            Enumeration localNodes = distHash.keys();
            while (localNodes.hasMoreElements()) {
                Node n = (Node)localNodes.nextElement();
                if(!locality.contains(n) && ((Integer)distHash.get(n)).intValue()<=r) {
                    n.justMadeLocal = true;
                    locality.addNodeWithEdges(n);
                    if (!fastFinishShift) {
                        try { Thread.currentThread().sleep(50); } 
                        catch (InterruptedException ex) {}
                    }
                }
            }
        }
    }

    private synchronized void unmarkNewAdditions() {
        TGForEachNode fen = new TGForEachNode() {
            public void forEachNode(Node n) {
                n.justMadeLocal=false;
            }    
        };
        locality.forAllNodes(fen);
    }

    /** The thread that gets instantiated for doing the locality shift animation. */
    class ShiftLocaleThread extends Thread {
        Hashtable distHash;
        Node focusNode;        
        int radius;
        int maxAddEdgeCount;
        int maxExpandEdgeCount;
        boolean unidirectional;
                
        ShiftLocaleThread(Node n, int r, int maec, int meec, boolean unid) {                                         
            focusNode = n;                        
            radius = r;             
            maxAddEdgeCount = maec;
            maxExpandEdgeCount = meec;
            unidirectional = unid;
            start();
         
        }
            
        public void run() {        
            synchronized (LocalityUtils.this) {
            //Make sure node hasn't been deleted
                if (!locality.getCompleteEltSet().contains(focusNode)) return;                        
                tgPanel.stopDamper();
                distHash = GESUtils.calculateDistances(
                             locality.getCompleteEltSet(),focusNode,radius,maxAddEdgeCount,maxExpandEdgeCount,unidirectional);                         
                try {
                    if (markDistantNodes(distHash.keySet())) {
                         for (int i=0;i<5&&!fastFinishShift;i++) {
                             Thread.currentThread().sleep(100);                      
                         }
                    }
                    removeMarkedNodes();
                    for (int i=0;i<1&&!fastFinishShift;i++) {
                        Thread.currentThread().sleep(100); 
                    }
                    addNearNodes(distHash,radius);
                    for (int i=0;i<4&&!fastFinishShift;i++) {
                        Thread.currentThread().sleep(100); 
                    }
                    unmarkNewAdditions();
                } catch ( TGException tge ) {
                    System.err.println("TGException: " + tge.getMessage());
                } catch (InterruptedException ex) {}    
                tgPanel.resetDamper();            
            }
        }
    }
            
    public void setLocale(Node n, final int radius, final int maxAddEdgeCount, final int maxExpandEdgeCount,
                          final boolean unidirectional) throws TGException {                                  
        if (n==null || radius<0) return;
        if(shiftLocaleThread!=null && shiftLocaleThread.isAlive()) {
            fastFinishShift=true; //This should cause last locale shift to finish quickly
            while(shiftLocaleThread.isAlive())
                try { Thread.currentThread().sleep(100); } 
                catch (InterruptedException ex) {}
        }
        if (radius == INFINITE_LOCALITY_RADIUS || n==null) {
            addAllGraphElts();
            tgPanel.resetDamper();
            return;
        }

        fastFinishShift=false;        
        shiftLocaleThread=new ShiftLocaleThread(n, radius, maxAddEdgeCount, maxExpandEdgeCount, unidirectional);
    }

    public void setLocale(Node n, final int radius) throws TGException {                                      
        setLocale(n,radius,1000,1000, false);
    }
    
    public synchronized void addAllGraphElts() throws TGException {
        locality.addAll();
    }
    
   /** Add to locale nodes that are one edge away from a given node.
     * This method does not utilize "fastFinishShift" so it's likely that 
     * synchronization errors will occur.
     */
    public void expandNode(final Node n) {
        new Thread() {
            public void run() {
                synchronized (LocalityUtils.this) {
                    if (!locality.getCompleteEltSet().contains(n)) return;                        
                    tgPanel.stopDamper();
                    for(int i=0;i<n.edgeCount();i++) {
                        Node newNode = n.edgeAt(i).getOtherEndpt(n);
                        if (!locality.contains(newNode)) {
                            newNode.justMadeLocal = true;
                            try {
                                locality.addNodeWithEdges(newNode);
                                Thread.currentThread().sleep(50); 
                            } catch ( TGException tge ) {
                                System.err.println("TGException: " + tge.getMessage());
                            } catch ( InterruptedException ex ) {}         
                        }
                        else if (!locality.contains(n.edgeAt(i))) {
                            locality.addEdge(n.edgeAt(i));
                        }
                    }
                    try { Thread.currentThread().sleep(200); } 
                    catch (InterruptedException ex) {}         
                    unmarkNewAdditions();
                    tgPanel.resetDamper();
                }
            }
        }.start();
    }
    
   /** Hides a node, and all the nodes attached to it. */
    public synchronized void hideNode( final Node hideNode ) {
        if (hideNode==null) return;
        new Thread() {
            public void run() {
                synchronized(LocalityUtils.this) {
                    if (!locality.getCompleteEltSet().contains(hideNode)) return;                                                               
                    
                    locality.removeNode(hideNode); //Necessary so that node is ignored in distances calculation.
                    if (hideNode==tgPanel.getSelect()) {
                        tgPanel.clearSelect();
                    }
                    
                                           
                    Collection subgraph = GESUtils.getLargestConnectedSubgraph(locality);
                    markDistantNodes(subgraph);                      
                    tgPanel.repaint();
                    try { Thread.currentThread().sleep(200); } 
                    catch (InterruptedException ex) {}         
                    removeMarkedNodes();                    
                    
                    tgPanel.resetDamper();
                }
            }
        }.start();
    }

   /** Opposite of expand node, works like hide node except that the selected node is not hidden.*/
    public synchronized void collapseNode( final Node collapseNode ) {
        if (collapseNode==null) return;
        new Thread() {
            public void run() {
                synchronized(LocalityUtils.this) {
                    if (!locality.getCompleteEltSet().contains(collapseNode)) return;                                                               
                    
                    locality.removeNode(collapseNode); //Necessary so that node is ignored in distances calculation.                                                               
                    Collection subgraph = GESUtils.getLargestConnectedSubgraph(locality);
                    markDistantNodes(subgraph);                                          
                    try {
                        locality.addNodeWithEdges(collapseNode); // Add the collapsed node back in.
                    } 
                    catch (TGException tge) { tge.printStackTrace(); }
                    tgPanel.repaint();
                    try { Thread.currentThread().sleep(200); } 
                    catch (InterruptedException ex) {}         
                    removeMarkedNodes();                    
                    
                    tgPanel.resetDamper();
                }
            }
        }.start();
    }
} // end com.touchgraph.graphlayout.LocalityUtils
