package edu.stanford.smi.protegex.owl.ui.clsproperties;

import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.util.Disposable;
import edu.stanford.smi.protegex.owl.model.*;
import edu.stanford.smi.protegex.owl.model.event.PropertyValueAdapter;
import edu.stanford.smi.protegex.owl.model.event.PropertyValueListener;
import edu.stanford.smi.protegex.owl.ui.ProtegeUI;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.util.*;

public class PropertyTreeNode extends DefaultMutableTreeNode
        implements Comparable, Disposable {

    private OWLNamedClass cls;

    private boolean inherited;

    private PropertyRestrictionsTree tree;

    private PropertyValueListener valueListener = new PropertyValueAdapter() {
        public void propertyValueChanged(RDFResource resource, RDFProperty property, Collection oldValues) {
            tree.refillLater();
        }
    };


    public PropertyTreeNode(PropertyRestrictionsTree tree, OWLNamedClass cls, RDFProperty property, boolean inherited) {
        setUserObject(property);
        this.inherited = inherited;
        this.cls = cls;
        this.tree = tree;
        addChildNodes();
        property.addPropertyValueListener(valueListener);
    }


    private void addChildNodes() {
        List restrictions = getRestrictions();
        for (Iterator it = restrictions.iterator(); it.hasNext();) {
            OWLRestriction restriction = (OWLRestriction) it.next();
            OWLAnonymousClass expressionRoot = restriction.getExpressionRoot();
            Collection subclasses = expressionRoot.getSubclasses(false);
            RDFSClass inheritedFromClass = (RDFSClass) subclasses.iterator().next();
            if (cls.equals(inheritedFromClass)) {
                inheritedFromClass = null;
            }
            RestrictionTreeNode node = new OldRestrictionTreeNode(tree, restriction, inheritedFromClass);
            add(node);
        }
    }


    public int compareTo(Object o) {
        PropertyTreeNode other = (PropertyTreeNode) o;
        if (!isInherited()) {
            if (other.isInherited()) {
                return -1;
            }
        }
        else {
            if (!other.isInherited()) {
                return 1;
            }
            else {
                if (getChildCount() > 0) {
                    if (other.getChildCount() == 0) {
                        return -1;
                    }
                }
                else {
                    if (other.getChildCount() > 0) {
                        return 1;
                    }
                }
            }
        }
        return getRDFProperty().compareTo(other.getRDFProperty());
    }


    public void dispose() {
        for (int i = 0; i < getChildCount(); i++) {
            getRestrictionTreeNode(i).dispose();
        }
        getRDFProperty().removePropertyValueListener(valueListener);
    }


    /**
     * A helper method to refill the child nodes.  This returns a List with relevant OWLRestrictions
     * that are defined in the class or its superclasses, including operands from
     * OWLIntersectionClasses.  The List is ordered by their distance from the top class, using
     * a breadth-first algorithm, so that the closest restriction appears at 0.
     *
     * @return a List of OWLRestriction instances
     */
    private List getRestrictions() {
        List rs = new ArrayList();
        List queue = new ArrayList();
        queue.add(cls);
        Set reachedClses = new HashSet();
        Set overloadedClses = new HashSet();
        boolean first = true;
        while (!queue.isEmpty()) {

            OWLNamedClass c = (OWLNamedClass) queue.get(0);
            reachedClses.add(c);
            queue.remove(0);

            for (Iterator it = c.getSuperclasses(false).iterator(); it.hasNext();) {
                RDFSClass superClass = (RDFSClass) it.next();
                if (superClass instanceof OWLRestriction) {
                    perhapsAddRestriction((OWLRestriction) superClass, rs, overloadedClses);
                }
                else if (superClass instanceof OWLIntersectionClass) {
                    OWLIntersectionClass intersectionClass = (OWLIntersectionClass) superClass;
                    for (Iterator ot = intersectionClass.getOperands().iterator(); ot.hasNext();) {
                        RDFSClass operand = (RDFSClass) ot.next();
                        if (operand instanceof OWLRestriction) {
                            perhapsAddRestriction((OWLRestriction) operand, rs, overloadedClses);
                        }
                    }
                }
                else if (superClass instanceof OWLNamedClass && !reachedClses.contains(superClass)) {
                    queue.add(superClass);
                }
            }
            if (first) {
                Collections.sort(rs, new Comparator() {
                    public int compare(Object o1, Object o2) {
                        RDFSClass directType1 = ((OWLRestriction) o1).getProtegeType();
                        RDFSClass directType2 = ((OWLRestriction) o2).getProtegeType();
                        return directType1.getName().compareTo(directType2.getName());
                    }
                });
                first = false;
            }
        }
        removeOverloadedAllRestrictions(rs);
        return rs;
    }


    public Icon getIcon() {
        RDFProperty property = getRDFProperty();
        if (isInherited()) {
            return property.getInheritedIcon();
        }
        else {
            return ProtegeUI.getIcon(property);
        }
    }


    public RDFProperty getRDFProperty() {
        return (RDFProperty) getUserObject();
    }


    public OWLNamedClass getRestrictedClass() {
        return cls;
    }


    public RestrictionTreeNode getRestrictionTreeNode(int index) {
        return (RestrictionTreeNode) getChildAt(index);
    }


    public OldRestrictionTreeNode getRestrictionTreeNode(OWLRestriction restriction) {
        for (int i = 0; i < getChildCount(); i++) {
            OldRestrictionTreeNode node = (OldRestrictionTreeNode) getChildAt(i);
            if (node.getRestriction().equals(restriction)) {
                return node;
            }
        }
        return null;
    }


    public OldRestrictionTreeNode getRestrictionTreeNode(String restrictionText) {
        for (int i = 0; i < getChildCount(); i++) {
            Object child = getChildAt(i);
            if (child instanceof OldRestrictionTreeNode) {
                OldRestrictionTreeNode node = (OldRestrictionTreeNode) child;
                if (node.getRestriction().getBrowserText().equals(restrictionText)) {
                    return node;
                }
            }
        }
        return null;
    }


    public boolean isInherited() {
        return inherited;
    }


    public boolean hasRestrictionOfType(Cls metaCls) {
        for (int i = 0; i < getChildCount(); i++) {
            if (metaCls.equals(getRestrictionTreeNode(i).getRestrictionMetaCls())) {
                return true;
            }
        }
        return false;
    }


    private void perhapsAddRestriction(OWLRestriction restriction, List rs, Set overloadedClses) {

        // Avoid duplicates
        String browserText = restriction.getBrowserText();
        for (Iterator it = rs.iterator(); it.hasNext();) {
            OWLRestriction owlRestriction = (OWLRestriction) it.next();
            if (browserText.equals(owlRestriction.getBrowserText())) {
                return;
            }
        }

        // Avoid generalizing OWLSomeValuesFrom restrictions
        if (restriction instanceof OWLSomeValuesFrom) {
            OWLSomeValuesFrom someValuesFrom = (OWLSomeValuesFrom) restriction;
            if (someValuesFrom.getSomeValuesFrom() instanceof RDFSNamedClass) {
                RDFSNamedClass namedClass = (RDFSNamedClass) someValuesFrom.getSomeValuesFrom();
                for (Iterator it = rs.iterator(); it.hasNext();) {
                    OWLRestriction owlRestriction = (OWLRestriction) it.next();
                    if (owlRestriction instanceof OWLSomeValuesFrom) {
                        OWLSomeValuesFrom other = (OWLSomeValuesFrom) owlRestriction;
                        if (other.getSomeValuesFrom() instanceof RDFSNamedClass) {
                            RDFSNamedClass subclass = (RDFSNamedClass) other.getSomeValuesFrom();
                            if (subclass.getSuperclasses(true).contains(namedClass)) {
                                return;
                            }
                        }
                    }
                }
            }
        }

        if (getRDFProperty().equals(restriction.getOnProperty())) {
            boolean isQCR = false;
            if (restriction instanceof OWLCardinalityBase) {
                OWLCardinalityBase base = (OWLCardinalityBase) restriction;
                isQCR = base.isQualified();
            }
            if (isQCR) {
                rs.add(restriction);
            }
            else if (!overloadedClses.contains(restriction.getProtegeType())) {
                if (restriction instanceof OWLCardinalityBase) {
                    overloadedClses.add(restriction.getProtegeType());
                }
                rs.add(restriction);
            }
        }
    }


    private void removeOverloadedAllRestrictions(List rs) {
        boolean changed = false;
        do {
            for (int i = 0; i < rs.size(); i++) {
                OWLRestriction restriction = (OWLRestriction) rs.get(i);
                if (restriction instanceof OWLAllValuesFrom &&
                        ((OWLAllValuesFrom) restriction).getFiller() instanceof RDFSClass) {
                    removeOverloadedAllRestrictions(rs, i);
                }
            }
        }
        while (changed);
    }


    private boolean removeOverloadedAllRestrictions(List rs, int index) {
        boolean changed = false;
        OWLAllValuesFrom allRestriction = (OWLAllValuesFrom) rs.get(index);
        RDFSClass superFiller = (RDFSClass) allRestriction.getFiller();
        RDFSClass superCls = allRestriction.getOwner();
        for (int i = index + 1; i < rs.size(); i++) {
            final OWLRestriction restriction = (OWLRestriction) rs.get(i);
            if (restriction instanceof OWLAllValuesFrom &&
                    restriction.getOnProperty().equals(allRestriction.getOnProperty())) {
                OWLAllValuesFrom a = (OWLAllValuesFrom) restriction;
                RDFSClass subFiller = (RDFSClass) a.getFiller();
                RDFSClass andir = a.getOwner();
                if (a.getFiller() instanceof RDFSClass &&
                        superFiller.isSubclassOf(subFiller) &&
                        !andir.equals(superCls)) {
                    rs.remove(i);
                    changed = true;
                }
            }
        }
        return changed;
    }
}
