/*
 * ObjectListBuilder.java
 *
 * Created on May 25, 2006, 11:11 AM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package usda.weru.util.diff.listbuilders;

import com.klg.jclass.table.TableDataComparator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import usda.weru.util.diff.*;

/**
 *
 * @author Joseph Levin
 */
public class ObjectListBuilder implements ListBuilder {

    /**
     *
     * @param engine
     * @param a
     * @param b
     * @return
     * @throws IncomparableObjectsException
     */
    @Override
    public DiffNode buildList(DiffEngine engine, Object a, Object b) throws IncomparableObjectsException {
        test(Object.class, a, b);

        DiffNode node = new DiffNode(a, b);
        node.setText("Object");
        applyExtras(node);
        return node;
    }

    /**
     *
     * @param objectType
     * @param a
     * @param b
     * @throws IncomparableObjectsException
     */
    protected void test(Class<?> objectType, Object a, Object b) throws IncomparableObjectsException {
        boolean test;
        if (a == null && b == null) {
            test = false;
            throw new IncomparableObjectsException("Unable to build list.  Both objects are null");
        } else if (a == null && objectType.isAssignableFrom(b.getClass())) {
            test = true;
        } else if (b == null && objectType.isAssignableFrom(a.getClass())) {
            test = true;
        } else if (objectType.isAssignableFrom(a.getClass()) && objectType.isAssignableFrom(b.getClass())) {
            test = true;
        } else {
            test = false;
        }

        if (!test) {
            Class<?> gcc = DiffEngine.greatestCommonClass(a, b);
            throw new IncomparableObjectsException("Unable to build list for " + gcc.getSimpleName()
                    + ".  Expected " + objectType.getSimpleName() + ".");
        }
    }

    /**
     *
     * @param node
     * @return
     */
    protected DiffNode applyExtras(DiffNode node) {
        return node;
    }

    /**
     *
     * @return
     */
    @Override
    public String toString() {
        return getClass().getSimpleName();
    }

    /**
     *
     * @param a
     * @param b
     * @param matchOr
     * @param sort
     * @return
     */
    @SuppressWarnings("unchecked")
    public Object[] match(Object[][] a, Object[][] b, boolean matchOr, boolean sort) {

        MatchComparator c = new MatchComparator(new TableDataComparator());
        if (sort) {
            Arrays.sort(a, c);
            Arrays.sort(b, c);
        }

        List<Object[]> matches = new ArrayList<Object[]>();
        boolean matched;
        for (Object[] a1 : a) {
            matched = false;
            Object[] aRow = a1;
            for (int bIndex = 0; bIndex < b.length; bIndex++) {
                Object[] bRow = b[bIndex];
                if (bRow == null) {
                    continue;
                }

                if (equals(aRow, bRow, matchOr)) {
					//Found a match.

                    //Add unmatched Bs that are before the A
                    for (int bIndex2 = 0; bIndex2 < bIndex; bIndex2++) {
                        Object[] bRow2 = b[bIndex2];
                        if (bRow2 != null) {
                            matches.add(pairObjects(null, bRow2[0]));
                            b[bIndex2] = null;
                        }
                    }

                    //Add the match
                    matches.add(pairObjects(aRow[0], bRow[0]));
                    matched = true;
                    //Set the b row to null so it is not reused.
                    b[bIndex] = null;

                    //We have a match so we can break out of the b loop.
                    break;
                }
            }
            //No match was found so add the A with a null B
            for (int bIndex = 0; bIndex < b.length; bIndex++) {
                Object[] bRow = b[bIndex];
                if (bRow != null && c.compare(aRow, bRow) > 0) {
                    matches.add(pairObjects(null, bRow[0]));
                    b[bIndex] = null;
                }
            }
            if (!matched) {
                matches.add(pairObjects(aRow[0], null));
            }
        }

        //Add unmatched Bs that are after all the A's
        for (int bIndex = 0; bIndex < b.length; bIndex++) {
            Object[] bRow = b[bIndex];
            if (bRow != null) {
                matches.add(pairObjects(null, bRow[0]));
                b[bIndex] = null;
            }
        }

        return matches.toArray();
    }

    private Object[] pairObjects(Object a, Object b) {
        Object[] temp = new Object[2];
        temp[0] = a;
        temp[1] = b;
        return temp;
    }

    private boolean equals(Object[] a, Object[] b, boolean matchOr) {

        int numOfLevels = a.length - 1;
        for (int level = 1; level <= numOfLevels; level++) {
            Object item1 = a[level];
            Object item2 = b[level];

            if (item1.equals(item2)) {
                if (matchOr) {
                    return true;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    class MatchComparator implements Comparator<Object> {

        protected Comparator<Object> c_comparator;

        MatchComparator(Comparator<Object> comparator) {
            c_comparator = comparator;
        }

        /**
         * A method that compares the provided object to the current object
         * @param o1 the first object to be compared.
         * @param o2 the second object to be compared.
         * @return 1 if o1 > o2, 0 if o1 == o2, -1 if o1 < o2
         */
        @Override
        public int compare(Object a, Object b) {
            Object[] o1 = (Object[]) a;
            Object[] o2 = (Object[]) b;

            int numOfLevels = o1.length - 1;
            int result = 0;
            for (int level = 1; level <= numOfLevels && result == 0; level++) {
                Object item1 = o1[level];
                Object item2 = o2[level];

                result = c_comparator.compare(item1, item2);
                //if (directions[level] == Sort.DESCENDING) {
                //    result = -result;
                //}
            }
            return (result);
        }
    }
}
