/*******************************************************************************
* 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 18-Oct-2004
 *
 * Created by Steve Zymler
 */
package uk.ac.ic.doc.kevlar.core.framework.typecheck;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import uk.ac.ic.doc.kevlar.api.programs.arguments.IInputNode;
import uk.ac.ic.doc.kevlar.api.programs.arguments.IOutputNode;
import uk.ac.ic.doc.kevlar.publicInterfaces.INode;

/**
 * @author Steve Zymler
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class TypeChecker implements ITypeChecker 
{
    private ITypeTree typeTree;
    
    public TypeChecker( ITypeTree typeTree )
    {
        this.typeTree = typeTree ;
    }
    
    /* (non-Javadoc)
     * @see uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeChecker#getTypeTree()
     */
    public synchronized ITypeTree getTypeTree() 
    {
        return typeTree ;
    }

    /* (non-Javadoc)
     * @see uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeChecker#setTypeTree(uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeTree)
     */
    public synchronized void setTypeTree(ITypeTree newTree) 
    {
        if( null == newTree )
        {
            throw new NullPointerException();
        }
        
        typeTree = newTree ;
    }
   
    /* (non-Javadoc)
     * @see uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeChecker#computeBoundsInput(java.util.Set, java.util.Set, uk.ac.ic.doc.kevlar.core.framework.typecheck.IBasicTypeClass, uk.ac.ic.doc.kevlar.core.framework.typecheck.IBasicTypeClass)
     */
    public synchronized IBounds computeBoundsInput(Set<ITypeClass> inputs, 
            						  Set<ITypeClass> outputs,
            						  IBasicTypeClass topBound, 
            						  ITypeClass newInput)
    {
    	// The final uper and lower bounds of the new IO function this function will
    	// calculate.
    	IInterfaceClass upperBound ;
    	IInterfaceClass lowerBound ;
    	    	    	
    	Set<IInterfaceClass> typeInputs 
			= new HashSet<IInterfaceClass>();    	  
    	List<IInterfaceClass> typeOutputs 
			= new ArrayList<IInterfaceClass>();
    	
    	// filter out the types into different sets depending wether they are basic 
    	// or parametered. 
        for( ITypeClass typeClass : inputs)
        {
        	if( typeClass instanceof IBasicTypeClass )
        	{
        		typeInputs.add( ((IBasicTypeClass) typeClass).getInterfaceClass() );
        	}
        	else if( typeClass instanceof IParameteredTypeClass )
        	{
        		typeInputs.add( 
        		  ((IParameteredTypeClass) typeClass).getInterfaceClassUpperBound() );
        	}        	
        }
        
        if( newInput instanceof IBasicTypeClass )
        {
            typeInputs.add( ((IBasicTypeClass) newInput).getInterfaceClass() );
        }
        else
        {
            typeInputs.add(
                ((IParameteredTypeClass) newInput).getInterfaceClassUpperBound() ); 
        }
        
        for( ITypeClass typeClass : outputs)
        {
        	if( typeClass instanceof IBasicTypeClass )
        	{
        		typeOutputs.add( ((IBasicTypeClass) typeClass).getInterfaceClass() );
        	}
        	else if( typeClass instanceof IParameteredTypeClass )
        	{
        		typeOutputs.add( 
        		   ((IParameteredTypeClass) typeClass).getInterfaceClassUpperBound() );
        	}        	
        }
        
        
        
        // CALCULATION OF LOWEST UPPER BOUND ON THE INPUTS
        // Let's calculate the lowest upper bound on the input's types. 
        // First we iterate over the basic input type set.
        IInterfaceClass lowestUpperBound 
			= typeTree.getLeastUpperBound( typeInputs );
        
        if( null == lowestUpperBound )
        {
            // unable to compute lowest upper bounds
            return null ;
        }
        
        if( typeTree.isDerivedFrom( lowestUpperBound, topBound.getInterfaceClass() ) )
        {
            lowerBound = lowestUpperBound ;
        }
        else
        {
            // unable to compute lowest upper bound
            return null ;
        }
        
        // CALCULATION OF HIGHEST LOWER BOUND ON THE OUTPUTS
        // Let's calculate the highest lower bound on te output's types.
        // First we iterate over the basic output type set.
                
        IInterfaceClass highestLowerBound = typeOutputs.get(0);        
        for( IInterfaceClass interfaceClass : typeOutputs )
        {
        	if( typeTree.isDerivedFrom( interfaceClass, highestLowerBound ) )
        	{
        	    highestLowerBound = interfaceClass ;
        	}
        	else if( typeTree.isDerivedFrom( highestLowerBound, interfaceClass ) )
        	{
        		continue ;
        	}
        	else
        	{
        		// Output types are not related 
        		return null ;
        	}
        }
        upperBound = highestLowerBound ;
         
        // Finally see if lowerbound is derived from upperbound
        if( typeTree.isDerivedFrom( lowerBound, upperBound ))
        {
        	return new Bounds( lowerBound, upperBound );
        }
                
        return null;
    }
    
    
    

    /* (non-Javadoc)
     * @see uk.ac.ic.doc.kevlar.core.framework.typecheck.ITypeChecker#computeBoundsOutput(java.util.Set, java.util.Set, uk.ac.ic.doc.kevlar.core.framework.typecheck.IBasicTypeClass, uk.ac.ic.doc.kevlar.core.framework.typecheck.IBasicTypeClass)
     */
    public synchronized IBounds computeBoundsOutput(Set<ITypeClass> inputs, 
            							Set<ITypeClass> outputs,
            							IBasicTypeClass topBound, 
            							ITypeClass newOutput)
    {
      	// The final uper and lower bounds of the new IO function this function will
    	// calculate.
    	IInterfaceClass upperBound ;
    	IInterfaceClass lowerBound ;
    	    	    	
    	Set<IInterfaceClass> typeInputs 
			= new HashSet<IInterfaceClass>();    	  
    	List<IInterfaceClass> typeOutputs 
			= new ArrayList<IInterfaceClass>();
    	
    	// filter out the types into different sets depending wether they are basic 
    	// or parametered. 
        for( ITypeClass typeClass : inputs)
        {
        	if( typeClass instanceof IBasicTypeClass )
        	{
        		typeInputs.add( ((IBasicTypeClass) typeClass).getInterfaceClass() );
        	}
        	else if( typeClass instanceof IParameteredTypeClass )
        	{
        		typeInputs.add( 
        		  ((IParameteredTypeClass) typeClass).getInterfaceClassUpperBound() );
        	}        	
        }
        for( ITypeClass typeClass : outputs)
        {
        	if( typeClass instanceof IBasicTypeClass )
        	{
        		typeOutputs.add( ((IBasicTypeClass) typeClass).getInterfaceClass() );
        	}
        	else if( typeClass instanceof IParameteredTypeClass )
        	{
        		typeOutputs.add( 
        		   ((IParameteredTypeClass) typeClass).getInterfaceClassUpperBound() );
        	}        	
        }
                       
        // CALCULATION OF LOWEST UPPER BOUND ON THE INPUTS
        // Let's calculate the lowest upper bound on the input's types. 
        // First we iterate over the basic input type set.
        IInterfaceClass lowestUpperBound 
			= typeTree.getLeastUpperBound( typeInputs );
        
        if( null == lowestUpperBound )
        {
            // unable to compute lowest upper bound
            return null ;
        }
        
        if( typeTree.isDerivedFrom( lowestUpperBound, topBound.getInterfaceClass() ) )
        {
            lowerBound = lowestUpperBound ;
        }
        else
        {
            // unable to compute lowest upper bound
            return null ;
        }
        
        // CALCULATION OF HIGHEST LOWER BOUND ON THE OUTPUTS
        // Let's calculate the highest lower bound on te output's types.
        // First we iterate over the basic output type set.
                
        IInterfaceClass highestLowerBound;
        if( newOutput instanceof IBasicTypeClass )
        {
            highestLowerBound = ((IBasicTypeClass) newOutput).getInterfaceClass();
        }
        else
        {
            highestLowerBound = 
                ((IParameteredTypeClass) newOutput).getInterfaceClassUpperBound(); 
        }
        for( IInterfaceClass interfaceClass : typeOutputs )
        {
        	if( typeTree.isDerivedFrom( interfaceClass, highestLowerBound ) )
        	{
        	    highestLowerBound = interfaceClass ;
        	}
        	else if( typeTree.isDerivedFrom( lowestUpperBound, interfaceClass ) )
        	{
        		continue ;
        	}
        	else
        	{
        		// Output types are not related 
        		return null ;
        	}
        }
        upperBound = highestLowerBound ;
         
        // Finally see if lowerbound is derived from upperbound
        if( typeTree.isDerivedFrom( lowerBound, upperBound ))
        {
        	return new Bounds( lowerBound, upperBound );
        }
                
        return null;
    }

	
	public synchronized boolean isPipeable(IInputNode nodeA, IOutputNode nodeB) 
	{
		ITypeClass typeA = nodeA.getType();
		ITypeClass typeB = nodeB.getType();
		
		IInterfaceClass classA;
		IInterfaceClass classB;
		
		if( typeA instanceof IBasicTypeClass )
		{
			classA = ((IBasicTypeClass) typeA).getInterfaceClass();
		}
		else
		{
			classA = ((IParameteredTypeClass) typeA).getInterfaceClassUpperBound();
		}
		
		if( typeB instanceof IBasicTypeClass )
		{
			classB = ((IBasicTypeClass) typeB).getInterfaceClass();
		}
		else
		{
			classB = ((IParameteredTypeClass) typeB).getInterfaceClassUpperBound();
		}
		
		return  typeTree.isDerivedFrom( classA, classB);				
	}
	
	public synchronized boolean isPipeable( IInterfaceClass classA, IInterfaceClass classB )
	{
		return  typeTree.isDerivedFrom( classA, classB );
	}
	
	public synchronized IInterfaceClass inferTypeOfNode(
			Map<String, IInterfaceClass> deltaLUBs,
			Map<String, IInterfaceClass> baseLUBs,
			INode fromNode )
	{
		ITypeClass typeFrom = fromNode.getType();
		
		IInterfaceClass classFrom;
				
		if( typeFrom instanceof IBasicTypeClass )
		{
			classFrom = ((IBasicTypeClass) typeFrom).getInterfaceClass();
		}
		else	
		{
			IParameteredTypeClass paramTC = (IParameteredTypeClass) typeFrom;
			String templateName =  paramTC.getTemplateName() ;
			
			if( deltaLUBs != null && deltaLUBs.containsKey( templateName ) ) 
			{
				classFrom = deltaLUBs.get( templateName );
			}
			else
			{
				classFrom = baseLUBs.get( templateName );
			}				
		}
		
		return classFrom ;				
	}
	
	public synchronized IInterfaceClass inferTypeOfNode(
			Map<String, IInterfaceClass> baseLUBs,
			INode fromNode )
	{
		ITypeClass typeFrom = fromNode.getType();
		
		IInterfaceClass classFrom;
				
		if( typeFrom instanceof IBasicTypeClass )
		{
			classFrom = ((IBasicTypeClass) typeFrom).getInterfaceClass();
		}
		else	
		{
			IParameteredTypeClass paramTC = (IParameteredTypeClass) typeFrom;
			String templateName =  paramTC.getTemplateName() ;
			
			classFrom = baseLUBs.get( templateName );		
		}
		
		return classFrom ;				
	}
	
	public synchronized boolean isPipeable(
			Map<String, IInterfaceClass> deltaLUBs,
			Map<String, IInterfaceClass> baseLUBs,
			INode fromNode, 
			INode toNode)
	{
		ITypeClass typeFrom = fromNode.getType();
		ITypeClass typeTo = toNode.getType();
		
		IInterfaceClass classFrom;
		IInterfaceClass classTo;
		
		if( typeTo instanceof IBasicTypeClass )
		{
			classTo = ((IBasicTypeClass) typeTo).getInterfaceClass();
		}
		else
		{
			classTo = ((IParameteredTypeClass) typeTo).getInterfaceClassUpperBound();
		}
		
		if( typeFrom instanceof IBasicTypeClass )
		{
			classFrom = ((IBasicTypeClass) typeFrom).getInterfaceClass();
			return  typeTree.isDerivedFrom( classFrom, classTo);
		}
		else
		{
			String templateName =  
			    ((IParameteredTypeClass)typeFrom).getTemplateName() ;
			
			if( deltaLUBs != null && deltaLUBs.containsKey( templateName ) ) 
			{
				classFrom = deltaLUBs.get( templateName );
			}
			else
			{
				classFrom = baseLUBs.get( templateName );
			}		
			
			if(classFrom != null)
			{
			    return  typeTree.isDerivedFrom( classFrom, classTo);
			}
			else
			{
			    IInterfaceClass upperBound = 
			        ((IParameteredTypeClass)typeFrom).getInterfaceClassUpperBound();
			    // This means that the LUB hasn't been decided for the FromNode's parameter name
			    return typeTree.isDerivedFrom( upperBound, classTo ) ||
			    	typeTree.isDerivedFrom(classTo, upperBound);
			}
		}
	}
	
	public synchronized IInterfaceClass getLeastUpperBound( Set<IInterfaceClass> typeInputs)
	{		 
		return typeTree.getLeastUpperBound( typeInputs );				
	}
	
	public synchronized boolean isPipeable(Map<String, IInterfaceClass> baseLUBs, 
								INode fromNode, 
								INode toNode) 
	{
		ITypeClass typeFrom = fromNode.getType();
		ITypeClass typeTo = toNode.getType();
		
		IInterfaceClass classFrom;
		IInterfaceClass classTo;
		
		if( typeTo instanceof IBasicTypeClass )
		{
			classTo = ((IBasicTypeClass) typeTo).getInterfaceClass();
		}
		else
		{
			classTo = ((IParameteredTypeClass) typeTo).getInterfaceClassUpperBound();
		}
		
		if( typeFrom instanceof IBasicTypeClass )
		{
			classFrom = ((IBasicTypeClass) typeFrom).getInterfaceClass();
			return  typeTree.isDerivedFrom( classFrom, classTo);
		}
		else
		{
			String templateName =  
			    ((IParameteredTypeClass)typeFrom).getTemplateName() ;
			
			classFrom = baseLUBs.get( templateName );
					
			if(classFrom != null)
			{
			    return  typeTree.isDerivedFrom( classFrom, classTo);
			}
			else
			{
			    IInterfaceClass upperBound = 
			        ((IParameteredTypeClass)typeFrom).getInterfaceClassUpperBound();
			    // This means that the LUB hasn't been decided for the FromNode's parameter name
			    return typeTree.isDerivedFrom( upperBound, classTo ) ||
			    	typeTree.isDerivedFrom(classTo, upperBound);
			}
		}
	}
}
