/*
 * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the
 * License at http://www.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.versioning;

import com.arsdigita.persistence.DataObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.persistence.metadata.Property;
import com.arsdigita.util.Assert;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Difference
 *
 * @author Rafael H. Schloming &lt;rhs@mit.edu&gt;
 * @version $Revision: #9 $ $Date: 2004/04/07 $
 **/

public class Difference {

    public final static String versionId = "$Id: //core-platform/dev/src/com/arsdigita/versioning/Difference.java#9 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private static final String[] STATES = {"Created", "Modified", "Deleted"};

    private static final int CREATED = 0;
    private static final int MODIFIED = 1;
    private static final int DELETED = 2;

    private static final boolean compare(Object o1, Object o2) {
        if (o1 == null || o2 == null) {
            return o1 == o2;
        } else {
            return o1.equals(o2);
        }
    }

    public class Change {

        private OID m_oid;
        private int m_state;
        private Map m_from = new HashMap();
        private Map m_to = new HashMap();
        private Map m_added = new HashMap();
        private Map m_removed = new HashMap();

        private Change(OID oid, int state) {
            m_oid = oid;
            m_state = state;
        }

        public OID getOID() {
            return m_oid;
        }

        public int getState() {
            return m_state;
        }

        boolean isEmpty() {
            if (isCreated() || isDeleted()) {
                return false;
            }

            if (m_from.size() > 0) {
                return false;
            }

            for (Iterator it = m_added.values().iterator(); it.hasNext(); ) {
                Collection c = (Collection) it.next();
                if (c.size() > 0) {
                    return false;
                }
            }

            for (Iterator it = m_removed.values().iterator(); it.hasNext(); ) {
                Collection c = (Collection) it.next();
                if (c.size() > 0) {
                    return false;
                }
            }

            return true;
        }

        Property toProp(String propertyName) {
            Property prop = m_oid.getObjectType().getProperty(propertyName);
            Assert.exists(prop, Property.class);
            return prop;
        }

        void setFrom(String prop, Object value) {
            setFrom(toProp(prop), value);
        }

        void setTo(String prop, Object value) {
            setTo(toProp(prop), value);
        }

        void clearFrom(String prop) {
            clearFrom(toProp(prop));
        }

        void clearFrom(Property prop) {
            m_from.remove(prop);
        }

        void setFrom(Property prop, Object value) {
            m_from.put(prop, value);
        }

        void setTo(Property prop, Object value) {
            if (compare(getFrom(prop), value)) {
                clearFrom(prop);
            } else {
                m_to.put(prop, value);
            }
        }

        public boolean isCreated() {
            return m_state == CREATED;
        }

        public boolean isDeleted() {
            return m_state == DELETED;
        }

        public boolean isModified(Property prop) {
            if (prop.isCollection()) {
                return getAdded(prop).size() > 0
                    || getRemoved(prop).size() > 0;
            } else {
                return m_from.containsKey(prop);
            }
        }

        private Object get(Map map, Property prop) {
            if (map.containsKey(prop)) {
                return map.get(prop);
            } else if (isDeleted()) {
                return null;
            } else {
                DataObject dobj = SessionManager.getSession().retrieve(m_oid);
                if (dobj == null) {
                    return null;
                } else {
                    Object result = dobj.get(prop.getName());
                    if (result instanceof DataObject) {
                        result = ((DataObject) result).getOID();
                    }
                    return result;
                }
            }
        }

        public Object getFrom(Property prop) {
            return get(m_from, prop);
        }

        public Object getTo(Property prop) {
            return get(m_to, prop);
        }

        private Collection getCollection(Map map, Property prop) {
            ArrayList result = (ArrayList) map.get(prop);
            if (result == null) {
                result = new ArrayList();
                map.put(prop, result);
            }

            return result;
        }

        Collection getAdded(String prop) {
            return getAdded(toProp(prop));
        }

        Collection getRemoved(String prop) {
            return getRemoved(toProp(prop));
        }

        public Collection getAdded(Property prop) {
            return getCollection(m_added, prop);
        }

        public Collection getRemoved(Property prop) {
            return getCollection(m_removed, prop);
        }

        public String toString() {
            return "<change state=" + STATES[m_state] + " oid=" + m_oid +
                "from=" + m_from + " to=" + m_to + " added=" + m_added +
                " removed=" + m_removed + ">";
        }

    }

    private ArrayList m_changes = new ArrayList();

    Change create(OID oid) {
        return new Change(oid, CREATED);
    }

    Change delete(OID oid) {
        return new Change(oid, DELETED);
    }

    Change modify(OID oid) {
        return new Change(oid, MODIFIED);
    }

    void addChange(Change change) {
        if (!change.isEmpty()) {
            m_changes.add(change);
        }
    }

    public Collection getChanges() {
        return m_changes;
    }

}
