/*
 * 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.truezip.file.TFile;
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.RunDirectory;
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();
    }

    /**
     *
     * @param a
     * @param b
     */
    public DiffEngine(Object a, Object b) {
        init();
        c_a = a;
        c_b = b;
    }

    /**
     *
     * @param a
     * @param 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(TFile.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(TFile.class, new WepsFileListBuilder());
        mapObjectListBuilder(OperationObjectListBuilder.ActionWrapper.class, new ActionWrapperListBuilder());
        mapObjectListBuilder(RunDirectory.class, new RunDirectoryListBuilder());

        //Map some stock differs
        mapObjectDiffer(Object.class, new ObjectDiffer());
        mapObjectDiffer(String.class, new StringDiffer());
        mapObjectDiffer(Number.class, new NumberDiffer());
    }

    /**
     *
     * @return
     */
    public DiffNode getNodeList() {
        return c_nodeList;
    }

    /**
     *
     * @param objectType
     * @param matcher
     */
    public void mapObjectListBuilder(Class<?> objectType, ListBuilder matcher) {
        c_listBuilderMap.put(objectType, matcher);
    }

    /**
     *
     * @param objectType
     * @return
     */
    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());
        }
    }

    /**
     *
     * @param objectType
     * @param differ
     */
    public void mapObjectDiffer(Class<?> objectType, Differ differ) {
        c_differMap.put(objectType, differ);
    }

    /**
     *
     * @param objectType
     * @return
     */
    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());
        }
    }

    /**
     *
     * @param a
     * @param b
     * @return
     */
    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;
        }
    }

    /**
     *
     */
    @Override
    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);
            }
        }
    }

    /**
     *
     * @param a
     * @param b
     * @return
     */
    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;
        }
    }

    /**
     *
     * @param engine
     * @param a
     * @param b
     * @return
     */
    @Override
    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);
            }
        }

    }

    /**
     *
     * @param a
     * @param b
     * @return
     */
    @Override
    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;
        }
    }

    /**
     *
     * @param a
     * @param b
     * @return
     */
    public boolean isSame(Object a, Object b) {
        return bitFlag(diff(a, b), Differ.DIFF_SAME);
    }

    //Static helper methods
    /**
     *
     * @param flags
     * @param flag
     * @return
     */
    public static boolean bitFlag(int flags, int flag) {
        return ((flags & flag) == flag);
    }

    /**
     *
     * @param flags
     * @param add
     * @return
     */
    public static int bitFlagAdd(int flags, int... add) {
        for (int t : add) {
            flags = flags | t;
        }
        return flags;
    }

}
