package org.exolab.castor.persist;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.castor.core.util.EnumerationIterator;
import org.castor.persist.TransactionContext;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.loader.AbstractMappingLoader;
import org.exolab.castor.mapping.xml.ClassMapping;
import org.exolab.castor.mapping.xml.FieldMapping;
import org.exolab.castor.persist.spi.Identity;

/**
 * Utility class that provides (mostly) static methods in relation to the functions
 * required by a {@link ClassMolder}.
 *
 */
public final class ClassMolderHelper {
    
    /**
     * Logger used for logging.
     */
    public static final Log LOG = LogFactory.getLog(ClassMolderHelper.class);
    
    private ClassMolderHelper() {
        // nothing to do
    }

    /**
     * A utility method which compare object.
     * @param o1 First object instance 
     * @param o2 Second object instance
     * @return True if the objects compared are equal
     */
    public static boolean isEquals(final Object o1, final Object o2) {
        if (o1 == o2) {
            return true;
        }
        if (o1 == null || o2 == null) {
            return false;
        }
        if (o1.equals(o2)) {
            return true;
        }
        // [oleg] is some special cases equals doesn't work properly
        if ((o1 instanceof java.math.BigDecimal)
                && ((java.math.BigDecimal) o1)
                        .compareTo((java.math.BigDecimal) o2) == 0) {
            return true;
        }
        if ((o1 instanceof java.sql.Timestamp)
                && (o2 instanceof java.sql.Timestamp)) {
            java.sql.Timestamp t1 = (java.sql.Timestamp) o1;
            java.sql.Timestamp t2 = (java.sql.Timestamp) o2;
            return (t1.getTime() == t2.getTime() && t1.getNanos() / 1000000 == t2
                    .getNanos() / 1000000);
        }

        if ((o1 instanceof byte[]) && (o2 instanceof byte[])) {
            return Arrays.equals((byte[]) o1, (byte[]) o2);
        }
        if ((o1 instanceof char[]) && (o2 instanceof char[])) {
            return Arrays.equals((char[]) o1, (char[]) o2);
        }
        return false;
    }

    /**
     * Utility method to compare collections for equality.
     * 
     * @param c1 collection one.
     * @param c2 collection two.
     * @return True if the collections are equal.
     */
    public static boolean isEquals(final Collection c1, final Collection c2) {
        if (c1 == c2) {
            return true;
        }
        if (c1 == null || c2 == null) {
            return false;
        }
        if (c1.containsAll(c2) && c2.containsAll(c1)) {
            return true;
        }
        return false;
    }

    /**
     * Return all the object identity of a Collection of object of the same
     * type.
     * 
     * @param tx the transaction context
     * @param molder class molder of the type of the objects
     * @param col a Collection or Vector containing
     * @return an <tt>ArrayList</tt>s which contains list of object identity
     */
    public static List<Identity> getIdsList(final TransactionContext tx,
            final ClassMolder molder, final Object col) {
        List<Identity> idList = new ArrayList<Identity>();
        
        if (col.getClass().isArray()) {
            Object[] colArr = (Object[]) col;
            for (int i = 0; i < colArr.length; i++) {
            	Identity id = molder.getIdentity(tx, colArr[i]);
                if (id != null) {
                    idList.add(id);
                }
            }
        } else if (col instanceof Enumeration) {
            Enumeration<Object> enumeration = (Enumeration<Object>) col;
            while (enumeration.hasMoreElements()) {
            	Identity id = molder.getIdentity(tx, enumeration.nextElement());
                if (id != null) {
                    idList.add(id);
                }
            }
        } else {
        	Iterator<Object> iter;
        	
        	if (col instanceof Iterator) {
                iter = (Iterator<Object>) col;
            } else if (col instanceof Collection) {
                iter = ((Collection<Object>) col).iterator();
            } else if (col instanceof Map) {
                iter = ((Map<Object, Object>) col).values().iterator();
            } else {
                throw new IllegalArgumentException(
                        "A Collection or Map is expected!");
            }
 
        	while (iter.hasNext()) {
            	Identity id = molder.getIdentity(tx, iter.next());
                if (id != null) {
                    idList.add(id);
                }
            }
        }

        return idList;
    }

    /**
     * Return the iterator on values of the specified Collection
     * or, return the iterator on values of the specified Map.
     * 
     * @param object - a Collection instance.
     */
    public static Iterator getIterator(final Object object) {
        if (object == null) {
            return new Iterator() {
                public boolean hasNext() {
                    return false;
                }

                public Object next() {
                    throw new NoSuchElementException();
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        } else if (object instanceof Collection) {
            return ((Collection) object).iterator();
        } else if (object instanceof Enumeration) {
            return new EnumerationIterator((Enumeration) object);
        } else if (object instanceof Iterator) {
            return (Iterator) object;
        } else if (object instanceof Map) {
            return ((Map) object).values().iterator();
        } else if (object.getClass().isArray()) {
            final class ArrayIterator implements java.util.Iterator {
                private Object[] _array;

                private int _i = 0;

                ArrayIterator(final Object[] array) {
                    this._array = array;
                }

                public boolean hasNext() {
                    return _i < _array.length;
                }

                public Object next() {
                    return _array[_i++];
                }

                public void remove() {
                    throw new UnsupportedOperationException();
                }
            }
            return new ArrayIterator((Object[]) object);
        } else {
            throw new IllegalArgumentException();
        }
    }

    /**
     * It is assumed the returned collection will not be modified. Any modification
     * to the returned collection may or may not affect the original collection or map.
     */
    public static Collection<Object> getAddedEntitiesList(final TransactionContext tx,
            final List<Identity> orgIds, final Object collection, final ClassMolder molder) {

        if (collection == null) {
            return new ArrayList<Object>(0);
        }

        if ((orgIds == null) || (orgIds.size() == 0)) {
        	if (collection.getClass().isArray()) {
                List<Object> added = new ArrayList<Object>();
                Object[] newArr = (Object[]) collection;
                for (int i = 0; i < newArr.length; i++) {
                    Object newValue = newArr[i];
                    added.add(newValue);
                }
                return added;
            } else if (collection instanceof Enumeration) {
                List<Object> added = new ArrayList<Object>();
                Enumeration<Object> newEnum = (Enumeration<Object>) collection;
                while (newEnum.hasMoreElements()) {
                    Object newValue = newEnum.nextElement();
                    added.add(newValue);
                }
                return added;
            } else if (collection instanceof Iterator) {
                List<Object> added = new ArrayList<Object>();
        		Iterator<Object> newIter = (Iterator<Object>) collection;
                while (newIter.hasNext()) {
                    Object newValue = newIter.next();
                    added.add(newValue);
                }
                return added;
            } else if (collection instanceof Collection) {
                return (Collection<Object>) collection;
        	} else if (collection instanceof Map) {
                return ((Map<Object, Object>) collection).values();
            } else {
                throw new IllegalArgumentException("Collection type "
                        + collection.getClass().getName() + " is not supported!");
            }
        }
        
        
        List<Object> added = new ArrayList<Object>();

        Set<Identity> orgSet = new HashSet<Identity>(orgIds);
    	if (collection.getClass().isArray()) {
            Object[] newArr = (Object[]) collection;
            for (int i = 0; i < newArr.length; i++) {
                Object newValue = newArr[i];
                Identity newId = molder.getIdentity(tx, newValue);
                if ((newId == null) || !orgSet.contains(newId)) {
                    added.add(newValue);
                }
            }
        } else if (collection instanceof Enumeration) {
            Enumeration<Object> newEnum = (Enumeration<Object>) collection;
            while (newEnum.hasMoreElements()) {
                Object newValue = newEnum.nextElement();
                Identity newId = molder.getIdentity(tx, newValue);
                if ((newId == null) || !orgSet.contains(newId)) {
                    added.add(newValue);
                }
            }
        } else {
        	Iterator<Object> newIter;
        	if (collection instanceof Iterator) {
                newIter = (Iterator<Object>) collection;
            } else if (collection instanceof Collection) {
                newIter = ((Collection<Object>) collection).iterator();
        	} else if (collection instanceof Map) {
                newIter = ((Map<Object, Object>) collection).values().iterator();
            } else {
                throw new IllegalArgumentException("Collection type "
                        + collection.getClass().getName() + " is not supported!");
            }

            while (newIter.hasNext()) {
                Object newValue = newIter.next();
                Identity newId = molder.getIdentity(tx, newValue);
                if ((newId == null) || !orgSet.contains(newId)) {
                	added.add(newValue);
                }
            }
        }

        return added;
     }

    /**
     * It is assumed the returned collection will not be modified. Any modification
     * to the returned collection may or may not affect the original collection or map.
     */
    public static List<Identity> getRemovedIdsList(final TransactionContext tx,
            final List<Identity> orgIds, final Object collection, final ClassMolder molder) {

        if ((orgIds == null) || (orgIds.size() == 0)) {
            return new ArrayList<Identity>(0);
        }

        if (collection == null) {
            return orgIds;
        }

        Set<Identity> newSet = new HashSet<Identity>();

        if (collection.getClass().isArray()) {
            Object[] newArr = (Object[]) collection;
            for (int i = 0; i < newArr.length; i++) {
                Object newObject = newArr[i];
                Identity newId = molder.getIdentity(tx, newObject);
                if (newId != null) {
                    newSet.add(newId);
                }
            }
        } else if (collection instanceof Enumeration) {
            Enumeration<Object> newEnum = (Enumeration<Object>) collection;
            while (newEnum.hasMoreElements()) {
                Object newObject = newEnum.nextElement();
                Identity newId = molder.getIdentity(tx, newObject);
                if (newId != null) {
                    newSet.add(newId);
                }
            }
        } else {
            Iterator<Object> newIter;
            if (collection instanceof Iterator) {
                newIter = (Iterator<Object>) collection;
            } else if (collection instanceof Collection) {
                Collection<Object> newCol = (Collection<Object>) collection;
                newIter = newCol.iterator();
            } else if (collection instanceof Map) {
                Collection<Object> newCol = ((Map<Object, Object>) collection).values();
                newIter = newCol.iterator();
            } else {
                throw new IllegalArgumentException("Collection type "
                        + collection.getClass().getName() + " is not supported!");
            }

            while (newIter.hasNext()) {
                Object newObject = newIter.next();
                Identity newId = molder.getIdentity(tx, newObject);
                if (newId != null) {
                    newSet.add(newId);
                }
            }
        }

        List<Identity> removedIds = new ArrayList<Identity>();

        Iterator<Identity> orgIter = orgIds.iterator();
        while (orgIter.hasNext()) {
        	Identity id = orgIter.next();
            if (!newSet.contains(id)) {
                removedIds.add(id);
            }
        }

        return removedIds;
    }
}
