/*******************************************************************************
* Copyright (c)                                                                *
*           2005   Tristan Allwood,                                            *
*           2005   Daniel Burke,                                               *
*           2005   Marc Hull,                                                  *
*           2005   Ekaterina Itskova,                                          *
*           2005   Steve Zymler                                                *
*                                                                              *
* Permission is hereby granted, free of charge, to any person obtaining a      *
* copy of this software and associated documentation files (the                *
* "Software"), to deal in the Software without restriction, including          *
* without limitation the rights to use, copy, modify, merge, publish,          *
* distribute, sublicense, and/or sell copies of the Software, and to           *
* permit persons to whom the Software is furnished to do so, subject to        *
* the following conditions:                                                    *
*                                                                              *
* The above copyright notice and this permission notice shall be included      *
* in all copies or substantial portions of the Software.                       *
*                                                                              *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS      *
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF                   *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.       *
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY         *
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,         *
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE            *
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                       *
*******************************************************************************/
/*
 * Created on 24-Nov-2004
 *
 * Created by Steve Zymle  r
 */
package uk.ac.ic.doc.kevlar.gui.internal.layout;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.eclipse.swt.graphics.Point;

import uk.ac.ic.doc.kevlar.publicUtil.collections.Tuple;


public class LayoutGrid {
    private List<ILayoutProgram> layoutGrid;    
    private Map<ILayoutPipe, Tuple<Point, Point>> mapPipes;
    
    static final int SPACING = 50;
    static final int PIPESPACING = 15;    
    private static final int DRIFT = 8;
    private Random r = new Random();
    
    public LayoutGrid()
    {
        layoutGrid = new ArrayList<ILayoutProgram>();        
        mapPipes = new HashMap<ILayoutPipe, Tuple<Point, Point>>();        
    }
    
    private int givePipeSpacing( )
    {
    	return 8 + (Math.abs(r.nextInt()) % 20);
    	
    }
        
    private void recalculatePipes( ILayoutProgram prog )
    {
    	for( ILayoutPipe pipe : mapPipes.keySet() ) 
    	{
    		Tuple<Point, Point> tup = mapPipes.get( pipe );
    		 
    		if( pipe != null && 
    			pipe.getDestination() != prog && pipe.getSource() != prog )
    		{    			
    			Point start = tup.getFirst();
    			Point end = tup.getSecond();
    			alterPipe(pipe, start, end );
    		}    		
    	}
    	
    }
    
    public void removePipe( ILayoutPipe pipe )
    {
    	mapPipes.remove( pipe );
    }
    
    public void addProgram( ILayoutProgram program , boolean isAutoPos)
    {
        if( ! layoutGrid.contains( program ) )
        {
            layoutGrid.add( program );
        }
        checkMove( program, isAutoPos );
        recalculatePipes( program );
    }
    
    private ILayoutProgram pointHit( Point p )
    {  
    	for( ILayoutProgram gridProg : layoutGrid )
        {
            ILayoutData gridProgData = gridProg.getLayoutData();
            
            if(  gridProgData.contains( p ) )
            {
            	return gridProg;
            }
        }
        return null ;
    }
    
    private ILayoutProgram lineHitHorz( Point start, Point end )
    {
    	List<ILayoutProgram> hits = new ArrayList<ILayoutProgram>();
    	for( ILayoutProgram gridProg : layoutGrid )
        {
            ILayoutData gridProgData = gridProg.getLayoutData();
            
            if( gridProgData.intersects( start, end ) )
            {
            	hits.add( gridProg );
            }
        }
    	
    	if( hits.isEmpty() )
    		return null ;
    	
    	Collections.sort( hits, new Comparator<ILayoutProgram>() {
			public int compare(ILayoutProgram o1, ILayoutProgram o2) {				
				return o1.getPosition().x - o2.getPosition().x;
			}

			public boolean equals(Object obj) {
				// TODO Auto-generated method stub
				return false;
			}
		});
    	    	
    	return hits.get(0) ;
    }
    
    private ILayoutProgram lineHitVert( Point start, Point end, boolean topdown )
    {
    	List<ILayoutProgram> hits = new ArrayList<ILayoutProgram>();
    	for( ILayoutProgram gridProg : layoutGrid )
        {
            ILayoutData gridProgData = gridProg.getLayoutData();
            
            if( gridProgData.intersects( start, end ) )
            {
            	hits.add( gridProg );
            }
        }
    	
    	if( hits.isEmpty() )
    		return null ;
    	
    	if( topdown )
    	{
	    	Collections.sort( hits, new Comparator<ILayoutProgram>() {
				public int compare(ILayoutProgram o1, ILayoutProgram o2) {				
					return o1.getPosition().y - o2.getPosition().y;
				}
	
				public boolean equals(Object obj) {
					// TODO Auto-generated method stub
					return false;
				}
			});
    	}
    	else
    	{
    	  	Collections.sort( hits, new Comparator<ILayoutProgram>() {
    		public int compare(ILayoutProgram o1, ILayoutProgram o2) {				
    			return o2.getPosition().y - o1.getPosition().y;
    		}
    	
    		public boolean equals(Object obj) {
    			// TODO Auto-generated method stub
    			return false;
    		}
    	});
    	}        
    	    	
    	return hits.get(0) ;
    }
    
    public void alterPipe( ILayoutPipe pipe, Point start, Point end )
    {
    	pipe.removePoints();
    	
    	Tuple<Point, Point> tup = mapPipes.get( pipe );
    	if( tup == null )
    	{
    		tup = new Tuple<Point, Point>(start, end);
    		mapPipes.put( pipe, tup );
    	}
    	else
    	{
    		tup.setFirst( start );
    		tup.setSecond( end );
    	}
        	        
        List<Point> points = new ArrayList<Point>();
        alterPipeAux( points, start, end, pipe.getSource(), 
        					 	pipe.getDestination(), true );
        
        for( Point p : points )
        {
        	pipe.addPoint( p );
        }
    }

    
    private void alterPipeAux(List<Point> pipe, Point start, Point end, 
    					  ILayoutProgram source, ILayoutProgram dest, 
    					  boolean isSourceDestPipe) {

        int seed = ((start.x + 37) * start.y) *
        ((end.x + 37) * end.y);
	    //System.out.println("Seed: " + seed + " source: " + source + " dest: " + dest);
		r.setSeed(seed);
    	
    	ILayoutProgram layoutProgPointHit ;
        ILayoutProgram layoutProgLineHit ;
        Point mid1 = null, mid2 = null ;
        
        if( source == dest )
        {
          	pipe.add( start );
          	int spacing1 = givePipeSpacing( );
          	int spacing2 = givePipeSpacing( );
          	int spacing3 = givePipeSpacing( );
          	pipe.add( new Point( start.x + spacing1, 
          						 start.y ));
          	pipe.add( new Point( start.x + spacing1,
          						 source.getPosition().y +
          						 source.getSize().y + 
          						 spacing2));
          	pipe.add( new Point( end.x - spacing3,
          						 source.getPosition().y +
          						 source.getSize().y + 
          						 spacing2));
          	pipe.add( new Point( end.x - spacing3, end.y));
          	pipe.add( end );
          	return;          	          			
         }
    
        
        if( start.x <= end.x )
        {
        	int spacing1 = givePipeSpacing( );
          	int spacing2 = givePipeSpacing( );
          	int spacing3 = givePipeSpacing( );          
        	
            pipe.add( start );
            
            int horzMiddleDist = start.x + (end.x - start.x)/2 + spacing3 % 6;
            mid1 = new Point( horzMiddleDist, start.y );
            
            ////// Check mid1 point for validity ///////////////////////            
            if( (layoutProgPointHit = pointHit( mid1 )) != null ) 
            {
            	mid1.x = layoutProgPointHit.getPosition().x - spacing1 ;
            	pipe.add( mid1 );            	
            	if( start.y < end.y)
            	{
            		mid1 = new Point( mid1.x, layoutProgPointHit.getPosition().y +
            									 layoutProgPointHit.getSize().y + 
            									 spacing2 );
            	}
            	else
            	{
            		mid1 = new Point( mid1.x, layoutProgPointHit.getPosition().y -							 					
							 					spacing2 );
            	}
            	alterPipeAux( pipe, mid1, end, source, dest, false );
            }
            
            else if( (layoutProgLineHit = lineHitHorz( start, mid1 )) != null )
            {
            	mid1.x = layoutProgLineHit.getPosition().x - spacing1 ;
            	pipe.add( mid1 );            	
            	if( start.y < end.y)
            	{
            		mid1 = new Point( mid1.x, layoutProgLineHit.getPosition().y +
            									 layoutProgLineHit.getSize().y + 
            									 spacing2 );
            	}
            	else
            	{
            		mid1 = new Point( mid1.x, layoutProgLineHit.getPosition().y -							 					
							 					spacing2 );
            	}
            	alterPipeAux( pipe, mid1, end, source, dest, false );
            }
            else
            {
            	pipe.add( mid1 );
            
                mid2 = new Point( horzMiddleDist, end.y );
            
                ////// Check mid2 point for validity ///////////////////////                       
                if( (layoutProgPointHit = pointHit( mid2 )) != null ) 
                {
                	if( start.y < end.y )
                	{
                		mid2.y = layoutProgPointHit.getPosition().y - spacing1 ;
                		pipe.add( mid2 );
                	
                		
                	}
                	else
                	{
                		mid2.y = layoutProgPointHit.getPosition().y + 
                				 layoutProgPointHit.getSize().y
                				 + spacing1 ;
                		
                		pipe.add( mid2 );                    	
                   	}
                	
                	mid2 = new Point( layoutProgPointHit.getPosition().x +
							 layoutProgPointHit.getSize().x + spacing2,
							 mid2.y );
                	                	                	
                	alterPipeAux( pipe, mid2, end, source, dest, false );
                }
		        else if( (layoutProgLineHit = lineHitVert( mid1, mid2, start.y < end.y )) != null )
		        {
		        	if( start.y < end.y )
                	{
                		mid2.y = layoutProgLineHit.getPosition().y - spacing1 ;                		
                		
                		pipe.add( mid2 );                	                		
                	}
                	else
                	{
                		mid2.y = layoutProgLineHit.getPosition().y + 
                				 layoutProgLineHit.getSize().y
                				 + spacing1 ;
                		
                		pipe.add( mid2 );                    	
                   	}
		        	
		        	mid2 = new Point( layoutProgLineHit.getPosition().x +
							 layoutProgLineHit.getSize().x + spacing2,
							 mid2.y );		        	      
		        			        	                	
                	alterPipeAux( pipe, mid2, end, source, dest, false );
                	
		        } 
		        else
		        {
		          	pipe.add( mid2 );		          	
		          	
		          	if( (layoutProgLineHit = lineHitHorz( mid2, end )) != null )
		            {		          		
		            	mid2 = new Point( layoutProgLineHit.getPosition().x - PIPESPACING, mid2.y);
		            	pipe.add( mid2 );            	
		            	if( start.y < end.y)
		            	{
		            		mid2 = new Point( mid2.x, layoutProgLineHit.getPosition().y +
		            									 layoutProgLineHit.getSize().y + 
		            									 spacing1 );
		            	}
		            	else
		            	{
		            		mid2 = new Point( mid2.x, layoutProgLineHit.getPosition().y -							 					
									 					spacing1 );
		            	}
		            	alterPipeAux( pipe, mid2, end, source, dest, false );
		            }
		            
		            pipe.add( end );
		        }
		    }                                                                
        }
        else
        {
        	int spacing1 = givePipeSpacing( );
          	int spacing2 = givePipeSpacing( );
          	int spacing3 = givePipeSpacing( );
          	int spacing4 = givePipeSpacing( );
          	int spacing5 = givePipeSpacing( );
        	
        	if( isSourceDestPipe )
        	{        	
	            pipe.add(end);
	            pipe.add( new Point( end.x - spacing1, end.y));
	            Point endOffset = null;
	            Point startOffset = null;
	            
	            
	            if( end.y < start.y )
	            {
	            	endOffset = new Point( end.x - spacing1, 
	            			dest.getPosition().y + 
	            			dest.getSize().y + spacing2);            
	            	startOffset = new Point( start.x + spacing3, 
	            			source.getPosition().y - spacing4 );
	            }
	            else
	            {
	            	endOffset = new Point( end.x - spacing1, 
	            			dest.getPosition().y -
	            			spacing2 );            
	            	startOffset = new Point( start.x + spacing3, 
	            			source.getPosition().y +
	            			source.getSize().y + 
	            			spacing4 );
	            }
	            alterPipeAux( pipe, endOffset, startOffset, source, dest, false );
	            pipe.add( new Point( start.x + spacing3, start.y ));
	            pipe.add( start );
        	}
        	else
        	{
        		List<Point> points = new ArrayList<Point>();
        		alterPipeAux( points, end, start, source, dest, false );
        		Collections.reverse( points );
        		pipe.addAll( points );
        	}
        }        	                
    }

    
    private void checkMove( ILayoutProgram program, boolean isAutoPos )
    {
        ILayoutData progData = program.getLayoutData() ;
        
        for( ILayoutProgram gridProg : layoutGrid )
        {
            ILayoutData gridProgData = gridProg.getLayoutData();
            
            if( gridProg != program && progData.intersects( gridProgData ))
            {
                int horzDist = Math.abs( gridProg.getPosition().x - program.getPosition().x );
                int vertDist = Math.abs( gridProg.getPosition().y - program.getPosition().y );
                
                Point moveTo ;
                if( (horzDist < vertDist && !isAutoPos) ||
                	(isAutoPos) ) 
                {
                     moveTo = new Point( program.getPosition().x,
						 				  gridProg.getPosition().y +
						 				  gridProg.getSize().y +
						 				  SPACING );
                }
                else
                {
                    moveTo = new Point( gridProg.getPosition().x +
                            			gridProg.getSize().x +
                            			SPACING,
                            			program.getPosition().y );
                            			
                }
                
                progData.setPosition( moveTo );
                program.setPosition( moveTo );
                
                checkMove( program, isAutoPos );
            }            
        }        
    }
    
    public void removeProgram( ILayoutProgram program )
    {
        layoutGrid.remove( program );
        recalculatePipes( program );
    }
	
	public void addPipe(ILayoutPipe pipe) {
		
		// do nothing
	}               
}
