/*
 * DiffEngine.java
 *
 * Created on May 24, 2006, 3:06 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package usda.weru.util.diff;

import de.schlichtherle.io.File;
import java.util.*;
import usda.weru.mcrew.CropObject;
import usda.weru.mcrew.ManageData;
import usda.weru.mcrew.OperationObject;
import usda.weru.mcrew.RowInfo;
import usda.weru.soil.IFC;
import usda.weru.util.diff.differs.*;
import usda.weru.util.diff.listbuilders.*;
import usda.weru.weps.RunFileData;


/**
 * This engine 
 * @author Joseph Levin
 */
public class DiffEngine extends Thread implements Differ, ListBuilder{
    private Dictionary<Class, ListBuilder> c_listBuilderMap; 
    private Dictionary <Class, Differ> c_differMap;          
    private DiffNode c_nodeList;
    private Object c_a;
    private Object c_b;
    
    public DiffEngine(){
        init();
    }
    
    public DiffEngine(Object a, Object b){
        init();
        c_a = a;
        c_b = b;        
    }
    
    public void setObjects(Object a, Object b){
        c_a = a;
        c_b = b;  
    }
    
    private void init(){
        c_listBuilderMap = new Hashtable <Class, ListBuilder> ();
        c_differMap = new Hashtable <Class, Differ> ();
        c_nodeList = new DiffNode();
        
        //Map some stock matchers
        mapObjectListBuilder(Object.class, new ObjectListBuilder());
    
        mapObjectListBuilder(WepsFileListBuilder.RunDirectory.class, new RunDirectoryListBuilder());
        mapObjectListBuilder(RunFileData.class, new RunFileDataListBuilder());
        mapObjectListBuilder(IFC.class, new SoilListBuilder());
            
        mapObjectListBuilder(RowInfo.class, new RowInfoListBuilder());
        mapObjectListBuilder(CropObject.class, new CropObjectListBuilder());
        mapObjectListBuilder(OperationObject.class, new OperationObjectListBuilder());
        mapObjectListBuilder(ManageData.class, new ManagementListBuilder());
        mapObjectListBuilder(File.class, new WepsFileListBuilder());
        mapObjectListBuilder(OperationObjectListBuilder.ActionWrapper.class, new ActionWrapperListBuilder());
       
        
        //Map some stock differs
        mapObjectDiffer(Object.class, new ObjectDiffer());
        mapObjectDiffer(String.class, new StringDiffer());
        mapObjectDiffer(Number.class, new NumberDiffer());
    }
    
    
    public DiffNode getNodeList(){
        return c_nodeList;
    }
    public void mapObjectListBuilder(Class objectType, ListBuilder matcher){
        c_listBuilderMap.put(objectType, matcher);
    }
    
    public ListBuilder getListBuilderForClass(Class objectType){
        ListBuilder lister = c_listBuilderMap.get(objectType);
        if (lister != null){
            return lister;
        }
        else{
            //Keep looking until a supported lister is found.
            return getListBuilderForClass(objectType.getSuperclass());
        }
    }
    
    public void mapObjectDiffer(Class objectType, Differ differ){
        c_differMap.put(objectType, differ);
    }
    
    public Differ getDifferForClass(Class objectType){
        Differ differ = c_differMap.get(objectType);
        if (differ != null){
            return differ;
        }
        else{
            //Keep looking until a supported differ is found.
            return getDifferForClass(objectType.getSuperclass());
        }
    }
    
    public static Class greatestCommonClass(Object a, Object b){
        
                
        if (a != null && b == null) {
            return a.getClass();
        }
        else if (b != null && a == null) {
            return b.getClass();
        }
        else if(a != null && b!= null){
            Class classA = a.getClass();
            Class classB = b.getClass();
            //Find the greatest common super class
            boolean toggle = true; 
            Class superClass = classA;
            while(!superClass.isAssignableFrom(classA) || !superClass.isAssignableFrom(classB)){
                if (toggle){
                    toggle = false;
                    superClass = classB.getSuperclass();
                }
                else{
                    toggle = true;
                    superClass = classA.getSuperclass();
                }
            }
            return superClass;            
        }     
        else{
            return Object.class;
        }
    }
       
    public void run(){
        buildNodeList();
        diffNodeList();
        interrupt();            
    }
        
    //Matching code.
    public void buildNodeList(){      
        c_nodeList = new DiffNode();
        c_nodeList.queueChild(c_a, c_b);
               
        while (c_nodeList.getBuildFlagRecursive()){
            buildListNode(c_nodeList);
        }
    }
    
    private void buildListNode(DiffNode node){
        ListIterator <DiffNode> iterator = node.iterator();
        while(iterator.hasNext()){
            DiffNode child = iterator.next();
            if (child.getBuildFlag()){
                Object a = child.getA();
                Object b = child.getB();
                DiffNode newNode = buildList(a, b);
                for (DiffNode subChild : child){
                    newNode.addChild(subChild);
                }
                iterator.set(newNode);
                newNode.setParent(node);
            }
            else if(child.getBuildFlagRecursive()){
                buildListNode(child);
            }
        }
    }
    
    public DiffNode buildList(Object a, Object b){
        try {
            Class matchOnClass = greatestCommonClass(a, b);
            ListBuilder lister = getListBuilderForClass(matchOnClass);
            DiffNode node = lister.buildList(this, a, b);                
            return node;
        } 
        catch (IncomparableObjectsException ex) {
            ex.printStackTrace();
            return null;
        }         
    }
    
    public DiffNode buildList(DiffEngine engine, Object a, Object b){
        return buildList(a, b);
    }
       
    //Diffing code
    public void diffNodeList(){
        while (c_nodeList.getDiffFlagRecursive()){
            diffNode(c_nodeList);                
        }
    }
    private void diffNode(DiffNode node){
        if (node.getDiffFlag()){
            //Diff this node            
            int diff = diff(node.getA(), node.getB());
            node.setDiff(diff);
            node.setDiffFlag(false);    //Diff is no longer required for this node.  
                                            //It may be required for a child node.   
        }
        else if (node.getDiffFlagRecursive()){
            //A child node requires diffing.
            for (DiffNode child : node){
                diffNode(child);
            }
        }

    }
    
    public int diff(Object a, Object b){
        try {          
            Class diffOnClass = greatestCommonClass(a, b);
            Differ differ = getDifferForClass(diffOnClass);
            int result = differ.diff(a, b);
            return result;
        } 
        catch (IncomparableObjectsException ex) {
            //We should never get here.  Everything should be able to resolve to the object differ if needed
            ex.printStackTrace();
            return Differ.DIFF_ERROR;
        }
    }
       

    public boolean isSame(Object a, Object b){
        return bitFlag(diff(a, b), Differ.DIFF_SAME);        
    }
    
    
    //Static helper methods
    public static boolean bitFlag(int flags, int flag){        
        return ((flags & flag) == flag);
    }
    
    public static int bitFlagAdd(int flags, int... add){
       for (int t : add){
           flags = flags | t;
       }
       return flags;
    }   
    
  
    
    
}
