/*******************************************************************************
 * Copyright (c) 2001, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Tada Takatoshi / Fujitsu - bug 245480 - provided initial patch
 *******************************************************************************/
package org.eclipse.wst.xsd.ui.internal.adapters;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.Assert;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.NotificationImpl;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.xsd.ui.internal.adt.actions.BaseSelectionAction;
import org.eclipse.wst.xsd.ui.internal.adt.actions.ShowPropertiesViewAction;
import org.eclipse.wst.xsd.ui.internal.adt.design.editparts.model.IActionProvider;
import org.eclipse.wst.xsd.ui.internal.adt.facade.IADTObjectListener;
import org.eclipse.wst.xsd.ui.internal.adt.facade.IModel;
import org.eclipse.wst.xsd.ui.internal.adt.outline.ITreeElement;
import org.eclipse.wst.xsd.ui.internal.common.actions.AddXSDComplexTypeDefinitionAction;
import org.eclipse.wst.xsd.ui.internal.common.actions.AddXSDElementAction;
import org.eclipse.wst.xsd.ui.internal.editor.Messages;
import org.eclipse.wst.xsd.ui.internal.editor.XSDEditorPlugin;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDAttributeGroupDefinition;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDImport;
import org.eclipse.xsd.XSDInclude;
import org.eclipse.xsd.XSDModelGroupDefinition;
import org.eclipse.xsd.XSDNamedComponent;
import org.eclipse.xsd.XSDPackage;
import org.eclipse.xsd.XSDRedefine;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSchemaDirective;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.util.XSDConstants;

public class XSDSchemaAdapter extends XSDBaseAdapter implements IActionProvider, IModel, IADTObjectListener
{
  protected List types = null;
  protected List children, allChildren;

  protected CategoryAdapter fDirectivesCategory;
  protected CategoryAdapter fElementsCategory;
  protected CategoryAdapter fAttributesCategory;
  protected CategoryAdapter fTypesCategory;
  protected CategoryAdapter fGroupsCategory;

  /**
   * Create all the category adapters
   * 
   * @param xsdSchema
   */
  protected void createCategoryAdapters(XSDSchema xsdSchema)
  {
    List directivesList = getDirectives(xsdSchema);
    List elementsList = getGlobalElements(xsdSchema);
    List attributesList = getAttributeList(xsdSchema);
    List groups = getGroups(xsdSchema);
    List types = getComplexTypes(xsdSchema);
    types.addAll(getSimpleTypes(xsdSchema));

    fDirectivesCategory = new CategoryAdapter(Messages._UI_GRAPH_DIRECTIVES, XSDEditorPlugin.getDefault().getIconImage("obj16/directivesheader"), directivesList, xsdSchema, CategoryAdapter.DIRECTIVES); //$NON-NLS-1$
    fDirectivesCategory.setAllChildren(directivesList);
    registerListener(fDirectivesCategory);

    fElementsCategory = new CategoryAdapter(Messages._UI_GRAPH_ELEMENTS, XSDEditorPlugin.getDefault().getIconImage("obj16/elementsheader"), elementsList, xsdSchema, CategoryAdapter.ELEMENTS);  //$NON-NLS-1$
    fElementsCategory.setAllChildren(getGlobalElements(xsdSchema, true));
    registerListener(fElementsCategory);

    fAttributesCategory = new CategoryAdapter(Messages._UI_GRAPH_ATTRIBUTES, XSDEditorPlugin.getDefault().getIconImage("obj16/attributesheader"), attributesList, xsdSchema, CategoryAdapter.ATTRIBUTES);   //$NON-NLS-1$
    fAttributesCategory.setAllChildren(getAttributeList(xsdSchema,true));
    registerListener(fAttributesCategory);

    fTypesCategory = new CategoryAdapter(Messages._UI_GRAPH_TYPES, XSDEditorPlugin.getDefault().getIconImage("obj16/typesheader"), types, xsdSchema, CategoryAdapter.TYPES);  //$NON-NLS-1$
    fTypesCategory.setAllChildren(getTypes(xsdSchema, true));
    registerListener(fTypesCategory);

    fGroupsCategory = new CategoryAdapter(Messages._UI_GRAPH_GROUPS, XSDEditorPlugin.getDefault().getIconImage("obj16/groupsheader"), groups, xsdSchema, CategoryAdapter.GROUPS); //$NON-NLS-1$
    fGroupsCategory.setAllChildren(getGroups(xsdSchema,true));
    registerListener(fGroupsCategory);
  }

  public List getTypes()
  {
    if (types == null)
    {
      types = new ArrayList();
      XSDSchema schema = (XSDSchema) target;
      List concreteComponentList = new ArrayList();
      for (Iterator i = schema.getContents().iterator(); i.hasNext();)
      {
        XSDConcreteComponent component = (XSDConcreteComponent) i.next();
        if (component instanceof XSDTypeDefinition)
        {
          concreteComponentList.add(component);
        }
      }
      populateAdapterList(concreteComponentList, types);
    }
    return types;
  }
  
  protected boolean isSameNamespace(String ns1, String ns2)
  {
    if (ns1 == null) ns1 = "";
    if (ns2 == null) ns2 = "";
    
    if (ns1.equals(ns2))
    {
      return true;
    }
    return false;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.wst.xsd.ui.internal.adt.outline.ITreeElement#getChildren()
   */
  public ITreeElement[] getChildren()
  {
    XSDSchema xsdSchema = (XSDSchema) getTarget();

    children = new ArrayList();

    // just set categoryadapters' children if category adapters are
    // already created
    if (fDirectivesCategory != null)
    {
      List directivesList = getDirectives(xsdSchema);
      List elementsList = getGlobalElements(xsdSchema);
      List attributesList = getAttributeList(xsdSchema);
      List groups = getGroups(xsdSchema);
      List types = getComplexTypes(xsdSchema);
      types.addAll(getSimpleTypes(xsdSchema));

      fDirectivesCategory.setChildren(directivesList);
      fDirectivesCategory.setAllChildren(directivesList);
      fElementsCategory.setChildren(elementsList);
      fElementsCategory.setAllChildren(getGlobalElements(xsdSchema, true));
      fAttributesCategory.setChildren(attributesList);
      fAttributesCategory.setAllChildren(getAttributeList(xsdSchema, true));
      fTypesCategory.setChildren(types);
      fTypesCategory.setAllChildren(getTypes(xsdSchema, true));
      fGroupsCategory.setChildren(groups);
      fGroupsCategory.setAllChildren(getGroups(xsdSchema, true));
    }
    else
    {
      createCategoryAdapters(xsdSchema);
    }

    children.add(fDirectivesCategory);
    children.add(fElementsCategory);
    children.add(fAttributesCategory);
    children.add(fTypesCategory);
    children.add(fGroupsCategory);

    return (ITreeElement[]) children.toArray(new ITreeElement[0]);
  }

  public void notifyChanged(final Notification msg)
  {
    class CategoryNotification extends NotificationImpl
    {
      protected Object category;

      public CategoryNotification(Object category)
      {
        super(msg.getEventType(), msg.getOldValue(), msg.getNewValue(), msg.getPosition());
        this.category = category;
      }

      public Object getNotifier()
      {
        return category;
      }

      public Object getFeature()
      {
        return msg.getFeature();
      }
    }

    if (children == null)
    {
      getChildren();
    }
    
    Object newValue = msg.getNewValue();
    
    Object oldValue = msg.getOldValue();
    // Bug 245480 - Deletion of Include, Import and Redefine is not reflected in the Outline view
    // We only want to refresh the Directives folder for any changes to XSDDirectives.  The first case covers
    // changes to one directive, whereas the missing case as reported in bug 245480 covers changes to a list
    // of directives.
    boolean updateDirectivesCategory = false;
    if (oldValue instanceof XSDSchemaDirective)
    {
      updateDirectivesCategory = true;
    }
    else if (oldValue instanceof Collection)
    {
      Iterator iterator = ((Collection) oldValue).iterator();
      while (iterator.hasNext())
      {
        Object obj = iterator.next();
        if (obj instanceof XSDSchemaDirective)
        {
          // if we find at least one directive, then we should refresh the folder
          updateDirectivesCategory = true;
          break;
        }
      }
    }
    
    if (newValue instanceof XSDInclude || newValue instanceof XSDImport || newValue instanceof XSDRedefine ||
        (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_Contents() && updateDirectivesCategory) || // handle the case for delete directive
         msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_IncorporatedVersions()) // updates to the imports/includes
    {
      CategoryAdapter adapter = getCategory(CategoryAdapter.DIRECTIVES);
      Assert.isTrue(adapter != null);
      XSDSchema xsdSchema = adapter.getXSDSchema();
      adapter.setChildren(getDirectives(xsdSchema));
      adapter.setAllChildren(getDirectives(xsdSchema));
      notifyListeners(new CategoryNotification(adapter), adapter.getText());
      return;
    }
    else if (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_ElementDeclarations())
    {
      CategoryAdapter adapter = getCategory(CategoryAdapter.ELEMENTS);
      Assert.isTrue(adapter != null);
      XSDSchema xsdSchema = adapter.getXSDSchema();
      adapter.setChildren(getGlobalElements(xsdSchema));
      adapter.setAllChildren(getGlobalElements(xsdSchema, true));
      notifyListeners(new CategoryNotification(adapter), adapter.getText());
      return;
    }
    else if (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_AttributeDeclarations() ||
             msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_AttributeGroupDefinitions())
    {
      CategoryAdapter adapter = getCategory(CategoryAdapter.ATTRIBUTES);
      Assert.isTrue(adapter != null);
      XSDSchema xsdSchema = adapter.getXSDSchema();
      adapter.setChildren(getAttributeList(xsdSchema));
      adapter.setAllChildren(getAttributeList(xsdSchema, true));
      notifyListeners(new CategoryNotification(adapter), adapter.getText());
      return;
    }
    else if (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_TypeDefinitions())
    {
      CategoryAdapter adapter = getCategory(CategoryAdapter.TYPES);
      Assert.isTrue(adapter != null);
      XSDSchema xsdSchema = adapter.getXSDSchema();
      List types = getComplexTypes(xsdSchema);
      types.addAll(getSimpleTypes(xsdSchema));

      adapter.setChildren(types);
      adapter.setAllChildren(getTypes(xsdSchema, true));
      notifyListeners(new CategoryNotification(adapter), adapter.getText());
      return;
    }
    else if (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_ModelGroupDefinitions())
    {
      CategoryAdapter adapter = getCategory(CategoryAdapter.GROUPS);
      Assert.isTrue(adapter != null);
      XSDSchema xsdSchema = adapter.getXSDSchema();
      adapter.setChildren(getGroups(xsdSchema));
      adapter.setAllChildren(getGroups(xsdSchema, true));
      notifyListeners(new CategoryNotification(adapter), adapter.getText());
      return;
    }
    else if (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_Annotations())
    {
      return;
    }
    else if (msg.getFeature() == XSDPackage.eINSTANCE.getXSDSchema_SchemaLocation())
    {
      notifyListeners(msg, null);
      return;
    }
    
    types = null;
    getTypes();

    super.notifyChanged(msg);
  }
  
  public void updateCategories()
  {
    // TODO: revisit this
    getChildren();
  }
  
  public CategoryAdapter getCategory(int category)
  {
    if (children == null) updateCategories(); // init categories
    int length = children.size();
    CategoryAdapter adapter = null;
    for (int i = 0; i < length; i++)
    {
      adapter = (CategoryAdapter) children.get(i);
      if (adapter.getGroupType() ==  category)
      {
        break;
      }
    }
    return adapter;
  }

  public List getDirectives(XSDSchema schema)
  {
    List list = new ArrayList();
    for (Iterator i = schema.getContents().iterator(); i.hasNext();)
    {
      Object o = i.next();
      if (o instanceof XSDSchemaDirective)
      {
        list.add(o);
      }
    }
    List adapterList = new ArrayList();
    populateAdapterList(list, adapterList);
    return adapterList;
  }
  
  public List getGlobalElements(XSDSchema schema, boolean showFromIncludes)
  {
    List elements = schema.getElementDeclarations();
    List list = new ArrayList();
    for (Iterator i = elements.iterator(); i.hasNext();)
    {
      XSDElementDeclaration elem = (XSDElementDeclaration) i.next();
      if (shouldShowComponent(elem, schema, showFromIncludes))
      {
        list.add(elem);
      }
    }
    List adapterList = new ArrayList();
    populateAdapterList(list, adapterList);
    return adapterList;
  }

  public List getGlobalElements(XSDSchema schema)
  {
    return getGlobalElements(schema, false);
  }

  /**
   * @param schema
   * @return
   */
  public List getComplexTypes(XSDSchema schema, boolean showFromIncludes)
  {
    List allTypes = schema.getTypeDefinitions();
    List list = new ArrayList();
    for (Iterator i = allTypes.iterator(); i.hasNext();)
    {
      XSDTypeDefinition td = (XSDTypeDefinition) i.next();
      if (td instanceof XSDComplexTypeDefinition)
      {
        XSDComplexTypeDefinition ct = (XSDComplexTypeDefinition) td;
        if (shouldShowComponent(ct, schema, showFromIncludes))
        {
          list.add(ct);
        }
      }
    }
    List adapterList = new ArrayList();
    populateAdapterList(list, adapterList);
    return adapterList;
  }

  public List getComplexTypes(XSDSchema schema)
  {
    return getComplexTypes(schema, false);
  }
  
  public List getTypes(XSDSchema schema, boolean showFromIncludes)
  {
    List list = getComplexTypes(schema, showFromIncludes);
    list.addAll(getSimpleTypes(schema, showFromIncludes));
    return list;
  }
  
  public List getAttributeGroupList(XSDSchema xsdSchema, boolean showFromIncludes)
  {
    List attributeGroupList = new ArrayList();
    for (Iterator i = xsdSchema.getAttributeGroupDefinitions().iterator(); i.hasNext();)
    {
      XSDAttributeGroupDefinition attrGroup = (XSDAttributeGroupDefinition) i.next();
      if (shouldShowComponent(attrGroup, xsdSchema, showFromIncludes))
      {
        attributeGroupList.add(attrGroup);
      }
    }
    List adapterList = new ArrayList();
    populateAdapterList(attributeGroupList, adapterList);
    return adapterList;
  }
  
  public List getAttributeGroupList(XSDSchema xsdSchema)
  {
    return getAttributeGroupList(xsdSchema, false);
  }

  public List getAttributeList(XSDSchema xsdSchema, boolean showFromIncludes)
  {
    List attributesList = new ArrayList();
    for (Iterator iter = xsdSchema.getAttributeDeclarations().iterator(); iter.hasNext();)
    {
      Object o = iter.next();
      if (o instanceof XSDAttributeDeclaration)
      {
        XSDAttributeDeclaration attr = (XSDAttributeDeclaration) o;
        if (attr != null)
        {
          if (attr.getTargetNamespace() != null)
          {
            if (!(attr.getTargetNamespace().equals(XSDConstants.SCHEMA_INSTANCE_URI_2001)))
            {
              if (shouldShowComponent(attr, xsdSchema,showFromIncludes))
              {
                attributesList.add(attr);
              }
            }
          }
          else
          {
            if (shouldShowComponent(attr, xsdSchema,showFromIncludes))
            {
              attributesList.add(attr);
            }
          }
        }
      }
    }
    
    attributesList.addAll(getAttributeGroupList(xsdSchema, showFromIncludes));
    
    List adapterList = new ArrayList();
    populateAdapterList(attributesList, adapterList);
    return adapterList;
  }
  
  public List getAttributeList(XSDSchema xsdSchema)
  {
    return getAttributeList(xsdSchema, false);
  }

  public List getSimpleTypes(XSDSchema schema, boolean showFromIncludes)
  {
    List allTypes = schema.getTypeDefinitions();
    List list = new ArrayList();
    for (Iterator i = allTypes.iterator(); i.hasNext();)
    {
      XSDTypeDefinition td = (XSDTypeDefinition) i.next();
      if (td instanceof XSDSimpleTypeDefinition)
      {
        XSDSimpleTypeDefinition st = (XSDSimpleTypeDefinition) td;
        if (shouldShowComponent(st, schema, showFromIncludes))
        {
          list.add(st);
        }
      }
    }
    List adapterList = new ArrayList();
    populateAdapterList(list, adapterList);
    return adapterList;
  }
  
  public List getSimpleTypes(XSDSchema schema)
  {
    return getSimpleTypes(schema, false);
  }

  public List getGroups(XSDSchema schema, boolean showFromIncludes)
  {
    List groups = schema.getModelGroupDefinitions();
    List list = new ArrayList();
    for (Iterator i = groups.iterator(); i.hasNext();)
    {
      XSDModelGroupDefinition group = (XSDModelGroupDefinition) i.next();
      if (shouldShowComponent(group, schema, showFromIncludes))
      {
        list.add(group);
      }
    }
    List adapterList = new ArrayList();
    populateAdapterList(list, adapterList);
    return adapterList;
  }
  
  public List getGroups(XSDSchema schema)
  {
    return getGroups(schema, false);
  }

  public String[] getActions(Object object)
  {
     Collection actionIDs = new ArrayList();
     actionIDs.add(AddXSDElementAction.ID);
     actionIDs.add(AddXSDComplexTypeDefinitionAction.ID);

     actionIDs.add(BaseSelectionAction.SEPARATOR_ID);
     actionIDs.add(ShowPropertiesViewAction.ID);
     return (String [])actionIDs.toArray(new String[0]);
  }

  public void propertyChanged(Object object, String property)
  {
    notifyListeners(object, property);
  }
  
  public Image getImage()
  {
    return XSDEditorPlugin.getXSDImage("icons/XSDFile.gif"); //$NON-NLS-1$
  }

  protected boolean shouldShowComponent(XSDNamedComponent component, XSDSchema schema, boolean showFromIncludes) 
  {
	  return isSameNamespace(component.getTargetNamespace(), schema.getTargetNamespace()) && (component.getRootContainer() == schema || showFromIncludes);
  }
}
