/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (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.mozilla.org/MPL/
 *
 * 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.
 *
 * The Original Code is Protege-2000.
 *
 * The Initial Developer of the Original Code is Stanford University. Portions
 * created by Stanford University are Copyright (C) 2007.  All Rights Reserved.
 *
 * Protege was developed by Stanford Medical Informatics
 * (http://www.smi.stanford.edu) at the Stanford University School of Medicine
 * with support from the National Library of Medicine, the National Science
 * Foundation, and the Defense Advanced Research Projects Agency.  Current
 * information about Protege can be obtained at http://protege.stanford.edu.
 *
 */

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import edu.stanford.smi.protege.event.ClsAdapter;
import edu.stanford.smi.protege.event.ClsEvent;
import edu.stanford.smi.protege.event.ClsListener;
import edu.stanford.smi.protege.event.FrameAdapter;
import edu.stanford.smi.protege.event.FrameEvent;
import edu.stanford.smi.protege.event.FrameListener;
import edu.stanford.smi.protege.exception.ProtegeException;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.model.KnowledgeBase;
import edu.stanford.smi.protege.model.Model;
import edu.stanford.smi.protege.server.RemoteSession;
import edu.stanford.smi.protege.server.Server;
import edu.stanford.smi.protege.server.ServerProject;
import edu.stanford.smi.protege.server.framestore.ServerFrameStore;
import edu.stanford.smi.protege.server.framestore.background.CacheRequestReason;
import edu.stanford.smi.protege.server.framestore.background.FrameCalculator;
import edu.stanford.smi.protege.ui.FrameComparator;
import edu.stanford.smi.protege.util.FrameWithBrowserText;
import edu.stanford.smi.protege.util.LazyTreeNode;
import edu.stanford.smi.protege.util.LocalizeUtils;
import edu.stanford.smi.protege.util.Log;
import edu.stanford.smi.protege.util.ProtegeJob;
import edu.stanford.smi.protegex.owl.model.OWLNamedClass;
import edu.stanford.smi.protegex.owl.model.RDFSClass;

public class ClassTreeWithBrowserTextNode extends LazyTreeNode {
       
    public ClassTreeWithBrowserTextNode(LazyTreeNode parentNode, FrameWithBrowserText userObj) {
        super(parentNode, userObj, parentNode.isSorted());
        Frame frame = userObj.getFrame();
        if (frame instanceof Cls) {
            attachListener((Cls)frame);          
        }
    }

    /*
     * Listener code
     */
    
    protected void attachListener(Cls cls) {
        if (cls != null) {
            cls.addClsListener(_clsListener);
            cls.addFrameListener(_frameListener);
        }
    }
    
    protected void detachListeners(Cls cls) {
        if (cls != null) {
            cls.removeClsListener(_clsListener);
            cls.removeFrameListener(_frameListener);
        }
    }
    
    private ClsListener _clsListener = new ClsAdapter() {
        public void directSubclassAdded(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            if (event.getSubclass().isVisible()) {
                childAdded(createFrameWithBrowserText(event.getSubclass()));
            }
        }


        public void directSubclassRemoved(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            if (event.getSubclass().isVisible()) {
                childRemoved(createFrameWithBrowserText(event.getSubclass()));
            }
        }


        public void directSubclassMoved(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            Cls subclass = event.getSubclass();
            FrameWithBrowserText subclassFbt = createFrameWithBrowserText(subclass);
            int index = (new ArrayList<FrameWithBrowserText>(getChildObjects())).indexOf(subclassFbt);
            if (index != -1) {
                childRemoved(subclassFbt);
                childAdded(subclassFbt, index);
            }
        }


        public void directInstanceAdded(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            notifyNodeChanged();
        }


        public void directInstanceRemoved(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            notifyNodeChanged();
        }


        public void templateFacetValueChanged(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            notifyNodeChanged();
        }


        public void directSuperclassAdded(ClsEvent event) {
            if (event.isReplacementEvent()) return;
            
            notifyNodeChanged();
        }
    };

    private FrameListener _frameListener = new FrameAdapter() {
        @Override
        public void frameReplaced(FrameEvent event) {
            Frame oldFrame = event.getFrame();
            Frame newFrame = event.getNewFrame();
            Cls cls = getCls();
            if (cls != null && cls.equals(oldFrame)) {
                reload(createFrameWithBrowserText(newFrame));              
            }
        }
        
        public void browserTextChanged(FrameEvent event) {
            if (event.isReplacementEvent()) return;
            notifyNodeChanged();
        }


        public void ownSlotValueChanged(FrameEvent event) {
            if (event.isReplacementEvent()) return;
            if (event.getSlot().getName().equals(Model.Slot.DIRECT_TYPES)) {
                // refresh the stale cls reference
                Cls cls = getCls().getKnowledgeBase().getCls(getCls().getName());
                reload(createFrameWithBrowserText(cls));
            }
            else {
                notifyNodeChanged();
            }
        }


        public void visibilityChanged(FrameEvent event) {
            if (event.isReplacementEvent()) return;
            notifyNodeChanged();
        }
    };

    protected void notifyNodeChanged() {
        notifyNodeChanged(this);
    }
    
    protected FrameWithBrowserText createFrameWithBrowserText(Frame frame) {
        if (frame == null) { return null; }
        return new FrameWithBrowserText(frame, frame.getBrowserText(), null);
    }
    
    /*
     * Get node children code 
     */
    
    
    @Override
    protected Collection<FrameWithBrowserText> getChildObjects() {
        Collection<FrameWithBrowserText> children;
        try {
            children = (Collection<FrameWithBrowserText>) 
                new GetChildObjectsJob(getCls().getKnowledgeBase(), getCls(), showHidden()).execute();            
        } catch (Throwable t) {
            Log.getLogger().log(Level.WARNING, "Could not get children of class " + getUserObject(), t);
            return new ArrayList<FrameWithBrowserText>();
        }     
        return children;
    }
 
    @Override
    protected LazyTreeNode createNode(Object o) {
        return new ClassTreeWithBrowserTextNode(this, (FrameWithBrowserText) o);
    }

    @Override   
    protected int getChildObjectCount() {
        if(showHidden()) {
            return getCls().getDirectSubclassCount();
        } else {
            /*
             * Unfortunately here we have to call the getChildObjects method
             * to get the size of the children.
             * This method is always called before getChildObjects, so 
             * the latter will be called twice which is expensive.
             * That is why we use a hack/optimization that ensures
             * that the children are retrieved only once, not twice.
             * For this, I had to make some methods from LazyTreeNode
             * protected, which I really don't like.
             * We might want to revist this in future versions.
             */
            Collection<FrameWithBrowserText> children = getChildObjects(); 
            //here come the hack/optimization; don't try this yourself
            loadChildObjects(children);
            setIsLoaded(true);
            return children.size();
        }
    }
   
    @Override
    protected Comparator getComparator() {        
        return null;
    }
    
    
    public Cls getCls() {
        FrameWithBrowserText fbt = (FrameWithBrowserText) getUserObject();        
        return (Cls)fbt.getFrame();
    }
    
    private boolean showHidden() {
        return getCls().getProject().getDisplayHiddenClasses();
    }
    
    
    protected void dispose() {        
        detachListeners(getCls());
        super.dispose();
    }
    
    
    /*
     * Protege job for computing and sorting children on the server
     */            
    
    private static class GetChildObjectsJob extends ProtegeJob {        
        private static final long serialVersionUID = -3887527946271511007L;
        
        private Cls cls;
        private boolean showHidden;        
        
        public GetChildObjectsJob(KnowledgeBase kb, Cls cls, boolean showHidden) {
            super(kb);
            this.cls = cls;
            this.showHidden = showHidden;
        }

        @Override
        public Object run() throws ProtegeException {
            Collection<Cls> clsChildren = null;  
            if (showHidden) {
                clsChildren = new HashSet(cls.getDirectSubclasses());
            } else {             
                clsChildren = new HashSet(cls.getVisibleDirectSubclasses());
                // Remove all equivalent classes that have other superclasses as well
                if (cls instanceof OWLNamedClass) {
                    Iterator equis = ((OWLNamedClass) cls).getEquivalentClasses().iterator();
                    while (equis.hasNext()) {
                        RDFSClass equi = (RDFSClass) equis.next();
                        if (equi instanceof OWLNamedClass && equi.getSuperclassCount() > 1) {
                            clsChildren.remove(equi);
                        }
                    }
                }
            }
            //TODO - add flag whether to sort?
            //sort
            List<Cls> clsChildrenList = new ArrayList<Cls>(clsChildren);
            Collections.sort(clsChildrenList, new FrameComparator());

            List<FrameWithBrowserText> fbtList = new ArrayList<FrameWithBrowserText>();
            for (Cls child : clsChildrenList) {
                addRequestsToFrameCalculator(child);
                fbtList.add(new FrameWithBrowserText(child, child.getBrowserText(), child.getDirectTypes()));
            }
            return fbtList;
        }
        
        //TODO: this can be taken out, if it proves too expensive for the server or client
        private void addRequestsToFrameCalculator(Frame frm) {
            if (!getKnowledgeBase().getProject().isMultiUserServer()) {
                return;
            }
            Server server = Server.getInstance();
            RemoteSession session = ServerFrameStore.getCurrentSession();
            ServerProject serverProject = server.getServerProject(getKnowledgeBase().getProject());
            ServerFrameStore serverFrameStore = (ServerFrameStore) serverProject.getDomainKbFrameStore(session);
            FrameCalculator fc = serverFrameStore.getFrameCalculator();
            
            fc.addRequest(frm, session, CacheRequestReason.USER_REQUESTED_FRAME_VALUES);        
        }
        
        
        @Override
        public void localize(KnowledgeBase kb) {
            super.localize(kb);
            LocalizeUtils.localize(cls, kb);
        }
        
    } 
    

}
