/*
 * Copyright (c) 2002-2003 IST-2004-2006-511731 ModelWare - ModelBus.
 * All rights reserved.
 *
 * This software is published under the terms of the ModelBus Software License
 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * A copy of ModelBus Software License is provided with this distribution in
 * doc/LICENSE.txt file.
 */

/*
 * Created on 16 juin 2005
 *
 */
package org.eclipse.mddi.modelbus.adapter.infrastructure.model_manipulation;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.activemq.ws.xmlbeans.servicegroup.AddDocument.Add;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notifier;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;




/**
 * @author Prawee Sriplakich (LIP6)
 *
 *
 */
public class ModelUtil {
    
    
    /**
     * Returns a set of elements that are direct instances of the specified metaclass.
     * The sybclass relation is not considerred.
     * 
     * @param emfobjs a collection of EObjects
     * @param typeName the name of the metaclass
     * @return
     * 
     * @see Add references
     *
     * TODO Add description
     */
    public static Collection findElementByType(Collection emfobjs, String typeName) {
       Collection set = new HashSet();     
       Iterator it = emfobjs.iterator();       
       while(it.hasNext()) {       
         EObject o =(EObject) it.next();
         if(o.eClass().getName().equals(typeName)) {
             set.add(o);    
           }     
       }
       return set;
    }
    
    
    
    
    
    public static Collection findElementByType_considerSubtypes(Collection emfobjs, EClass type) {
        Collection set = new HashSet();     
        Iterator it = emfobjs.iterator();       
        while(it.hasNext()) {       
          EObject o =(EObject) it.next();
          if(isInstanceOf(o, type)) {
              set.add(o);    
          }     
        }
        return set;        
    }
    
    public static boolean isInstanceOf(EObject o, EClass c) {
       EClass subc = o.eClass();
       if(subc.equals(c)) return true;
       if(subc.getEAllSuperTypes().contains(c)) return true;
       return false;
    }
    
    public static EObject findElementByName(Collection model, String name) {
        for(Iterator it=model.iterator(); it.hasNext(); ) {
            EObject o = (EObject) it.next();
            EStructuralFeature f = o.eClass().getEStructuralFeature("name"); 
            //findStructuralFeature(o.eClass(), "name");
            if(f!=null) {
              Object _name = o.eGet(f);    
              if(_name!=null && _name.equals(name)) {
                return o;    
              }
            }
        }
        return null;
    }    
    
    /**
     * 
     * findStructuralFeature
     * 
     * @param c
     * @param featureName
     * @return
     * @deprecated use EClass.getEStructuralFeature(String name) instead
     *
     */
    public static EStructuralFeature findStructuralFeature(EClass c, String featureName) {
        EStructuralFeature f = null;
        for(Iterator it=c.getEAllStructuralFeatures().iterator();it.hasNext(); ) {
            f = (EStructuralFeature) it.next();
            if(f.getName().equals(featureName)) {
              return f;    
            }
        }        
        return null;
    }	    
    
    
    
    /**
     * Returns a set containing elements in <code>emfobjs</code> 
     * and their subelements.
     * 
     * 
     * @param model
     * @return
     */
    public static Collection flattenHirachicalCollection(Collection model) {
        Collection target = new HashSet();
        Iterator it = model.iterator();       
        while(it.hasNext()) {       
          EObject o =(EObject) it.next();
          target.add(o);    
          Iterator it2 = o.eAllContents();
          while(it2.hasNext()) {
              EObject o2 =(EObject) it2.next();
              target.add(o2);   
          }       
        }
        return target;
    }

    
    
    public static Collection getTopElementsFromResources(Resource[] resources) {
        Collection result = new Vector();
        for(int i=0; i<resources.length; i++) {
            Resource r = resources[i];
            result.addAll(r.getContents());
        }
        return result;
    }
    
    /**
     * Find top-level elements in the <code>source</code> 
     * and put them in the return collection.
     * 
     * @param model
     * @return
     */    
    public static Collection getTopElements(Collection model) {
        Collection target = new HashSet();
	    Iterator it = model.iterator();
	    while(it.hasNext()) {
	        EObject o = (EObject) it.next();
	        EObject topElem = getTopElement(o);
	        if(!target.contains(topElem)) {
	          target.add(topElem);
	        }
	    }    
        return target;          
    }
    
    

    
    public static EObject getTopElement(EObject o) {
        EObject topElem = o;
        while(topElem.eContainer()!=null) {
            topElem = topElem.eContainer();
        }       
        return topElem;
    }
    
    
    
    
    /**
     * Check whether this collection contains only EObjects
     * 
     * @param c
     * @return false if there is one or more instances that are not EObjects
     * 		   true if all instances are EObjects or the collection is empty 
     */
    public static boolean isEObjectCollection(Collection c) {
	    Iterator it = c.iterator();
	    while(it.hasNext()) {
	        Object o = it.next();
	        if(!(o instanceof EObject)) {
	          return false;
	        }    
	    }
	    return true;        
    }




    public static Object getFeatureValue(EObject o, String featureName) {
      EStructuralFeature f = o.eClass().getEStructuralFeature(featureName); 
      //findStructuralFeature(o.eClass(), featureName);
      if(f==null) {
        return null;    
      }
      return o.eGet(f);    
    }




    public static Object createFromString(String primitiveType, String value) {
        if(primitiveType.equalsIgnoreCase("string")) {
          return value;   
        } else if (primitiveType.equalsIgnoreCase("boolean")) {
          return Boolean.valueOf(value);  
        } else if (primitiveType.equalsIgnoreCase("integer")) {
          return new Integer(Integer.parseInt(value));  
        } else if (primitiveType.equalsIgnoreCase("double")) {
          return new Double(Double.parseDouble(value));  
        }
        System.err.println("unknown primitive " + primitiveType);
        return null;
    }




    public static Collection getTopElemsInFragment(Collection fragment) {
      Collection result = new HashSet();
      for(Iterator it = fragment.iterator(); it.hasNext();) {
        EObject e = ModelUtil.getTopElemInFragment( (EObject)it.next() ,fragment);      
        result.add(e);
      }
      return result;
    }




    /**
     * Returns the top-most element that is included in this model.
     */
    public static EObject getTopElemInFragment(EObject currentElem, Collection fragment) {
        EObject topElem = currentElem;
        while(topElem.eContainer()!=null &&
                fragment.contains(topElem.eContainer()) ) {
              topElem = topElem.eContainer();    
        }
        return topElem;
    }





    /**
     * 
     * find an Adapter corresponding to a specified type
     * 
     * @param target
     * @param type
     * @return
     */
    public static Adapter findAdapter(Notifier target, Object type)
    {
      for (Iterator adapters = target.eAdapters().iterator(); adapters.hasNext(); )
      {
        Adapter adapter = (Adapter)adapters.next();
        if (adapter.isAdapterForType(type))
        {
          return adapter;
        }
      }
      return null;
    }





    /**
     * Add resources that are crossed-referenced by the resources to the
     * specified set. <br>
     * 
     * option "ignoredUriPrefixSet": is a set of URI prefixes to be ignored.
     * The cross-referenced resources 
     * that have those prefixes
     * will NOT be included in the result.
     * <br>
     * 
     * @param resources
     * @param ignoredUriPrefixSet is a set of string
     * 
     * 
     */
    public static void addCrossReferencedResources(Set resources,
            Collection ignoredUriPrefixSet) {
        // the queue contains model elements to be examined.
        // add all model elements to the queue.
        List queue = new Vector();
        for (Iterator it = resources.iterator(); it.hasNext();) {
            Resource r = (Resource) it.next();
            for (Iterator it2 = r.getAllContents(); it2.hasNext();) {
                EObject containedObject = (EObject) it2.next();
                queue.add(containedObject);
            }
        }
        // examine elements in the queue
        while (!queue.isEmpty()) {
            EObject o = (EObject) queue.remove(0);
            for (Iterator it = o.eCrossReferences().iterator(); it.hasNext();) {
                EObject referencedObject = (EObject) it.next();
                
                if(referencedObject instanceof EFactory) {
                    // the EFactory need not be serialized.
                    continue;
                }
                if(referencedObject.eIsProxy()) {
                    // the Proxy are the object that can not be resolved.
                    // therefore, it does not need to be serialized.
                    continue;
                }
                
                Resource cr = referencedObject.eResource();
                if (cr!=null && !resources.contains(cr) ) {
    
                    if (!ModelUtil.uriBeginsWith(cr.getURI(), ignoredUriPrefixSet) ) {
                        resources.add(cr);
                        // we need to examine the cross-ref resource from this
                        // resouce too.
                        // Therefore, we add all elements in this resource in
                        // the queue.
                        for (Iterator it2 = cr.getAllContents(); it2.hasNext();) {
                            queue.add(it2.next());
                        }
                    }
                } else if(cr==null) {
                    // find whether the default resource already exists.
                    cr = null;
                    for(Iterator it2 = resources.iterator(); it2.hasNext(); ) {
                        Resource r = (Resource) it2.next();
                        if(ModelBusResourceSet.MB_DEFAULT_RES_URI.equals(r.getURI())) {
                            cr = r;
                            break;
                        }
                    }
                    // no default resource
                    if(cr==null) {
                      ResourceSet rSet = new ModelBusResourceSet();
                      cr = rSet
                            .createResource(ModelBusResourceSet.MB_DEFAULT_RES_URI);
                    }
                    EObject top = getTopElement(referencedObject);
                    cr.getContents().add(top);
                    resources.add(cr);
                    // we need to examine the cross-ref resource from this
                    // resouce too.
                    // Therefore, we add all elements in this resource in
                    // the queue.
                    for (Iterator it2 = top.eAllContents(); it2.hasNext();) {
                        queue.add(it2.next());
                    }                    
                } 
            } // end for
        }
    }



    /**
     * 
     * Obtain a set of resources that contained the specified objects. For
     * objects has no resource, a default resource is created for containing
     * them.
     * <br><br>
     * option "skipCrossReferences": It true, the resources that are referenced 
     * by the specified elements will NOT be included in the result. 
     * <br><br>
     * option "ignoredUriPrefixSet": is a set of URI prefixes to be ignored.
     * The cross-referenced resources 
     * that have those prefixes
     * will NOT be included in the result.
     * <br>
     * @param emfobjs
     * @param ignoredUriPrefixSet (set of String)
     * @return a set of container resources
     * 
     * 
     */
    public static Resource[] getResourcesFromObjects(Collection emfobjs,
            boolean skipCrossReferences, Collection ignoredUriPrefixSet) {
        
        Set resources = new HashSet();
    
        Collection orphanTopElements = new HashSet();
        for (Iterator it = emfobjs.iterator(); it.hasNext();) {
            EObject o = (EObject) it.next();
            Resource r = o.eResource();
            if (r == null) {
                // add this object to resource;
                EObject top = getTopElement(o);
                //System.out.println("*********  add orphan " +top +" already contained? " +orphanTopElements.contains(top));
                orphanTopElements.add(top);
            } else {
                resources.add(r);
            }
        }
        // If there are orphan elements, a new resource will be created to contain those elements.   
        if (!orphanTopElements.isEmpty() ) {
            // find whether the default resource already exists.
            Resource defaultResource = null;
            for(Iterator it = resources.iterator(); it.hasNext(); ) {
                Resource r = (Resource) it.next();
                if(ModelBusResourceSet.MB_DEFAULT_RES_URI.equals(r.getURI())) {
                    defaultResource = r;
                    break;
                }
            }
            // no default resource
            if(defaultResource==null) {
              ResourceSet rSet = new ModelBusResourceSet();
              defaultResource = rSet
                    .createResource(ModelBusResourceSet.MB_DEFAULT_RES_URI);
            }
            defaultResource.getContents().addAll(orphanTopElements);
            
            resources.add(defaultResource);            
        }
        // if option skipCrossReferences is false, the cross-referenced resources are searched
        if (!skipCrossReferences) {
            addCrossReferencedResources(resources, ignoredUriPrefixSet);
        }        
        return (Resource[]) resources.toArray(new Resource[resources.size()]);
    }

    

    
    /**
     * 
     * test that the URI begins with a specified prefix.
     * 
     * @param uri
     * @param prefixSet a set of possible prefix. for example { "http://", "pathmap:" }
     * @return
     * 
     *
     */
    public static boolean uriBeginsWith(URI uri, Collection prefixSet) {
        if(prefixSet==null) return false;
        
        String uriString = uri.toString();
        for(Iterator it=prefixSet.iterator(); it.hasNext(); ) {
            String prefix = (String) it.next();
            if(uriString.startsWith(prefix)) {
                return true;
            }
        }
        return false;
    }       

}
