/**
 * Copyright (c) 2009 TIBCO Software Inc. and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   Adrian Price
 */
package org.eclipse.emf.ecore.util;

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

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;


/**
 * Composes multiple switches in order to handle instances of classes defined in multiple packages.
 * @since 2.7
 */
public class ComposedSwitch<T> extends Switch<T>
{
  private final Map<EPackage, Switch<T>> registry = new HashMap<EPackage, Switch<T>>();
  private final List<Switch<T>> switches = new ArrayList<Switch<T>>();

  /**
   * Creates a new ComposedSwitch.
   */
  public ComposedSwitch()
  {
    super();
  }

  /**
   * Creates a new ComposedSwitch.
   * @param switches The switches to which the composed switch will delegate. The list should generally include switches for all the
   * packages the composed switch will be expected to handle.
   */
  public ComposedSwitch(Collection<? extends Switch<T>> switches)
  {
    for (Switch<T> sw : switches)
    {
      addSwitch(sw);
    }
  }

  public void addSwitch(Switch<T> sw)
  {
    synchronized (switches)
    {
      if (!switches.contains(sw))
      {
        switches.add(sw);
      }
    }
  }

  public void removeSwitch(Switch<T> sw)
  {
    synchronized (switches)
    {
      if (switches.contains(sw))
      {
        switches.remove(sw);
        Iterator<Switch<T>> it = registry.values().iterator();
        while (it.hasNext())
        {
          if (it.next() == sw)
          {
            it.remove();
          }
        }
      }
    }
  }

  @Override
  protected T doSwitch(EClass theEClass, EObject theEObject)
  {
    Switch<T> delegate = findDelegate(theEClass.getEPackage());
    if (delegate == null)
    {
      List<EClass> eSuperTypes = theEClass.getESuperTypes();
      return eSuperTypes.isEmpty() ? defaultCase(theEObject) : doSwitch(eSuperTypes.get(0), theEObject);
    }
    else
    {
      T result = delegatedDoSwitch(delegate, theEClass, theEObject);
      return result == null ? defaultCase(theEObject) : result;
    }
  }
  
  /**
   * Call delegate.{@link Switch#doSwitch(EClass, EObject) doSwitch}(theEClass, theEObject).
   * @since 2.8
   */
  protected T delegatedDoSwitch(Switch<T> delegate, EClass theEClass, EObject theEObject)
  {
    return delegate.doSwitch(theEClass, theEObject);
  }

  /**
   * Finds a suitable delegate for the given package.
   * @since 2.8
   */
  protected Switch<T> findDelegate(EPackage ePackage)
  {
    synchronized (switches)
    {
      Switch<T> delegate = registry.get(ePackage);
      if (delegate == null && !registry.containsKey(ePackage))
      {
        for (Switch<T> sw : switches)
        {
          if (sw.isSwitchFor(ePackage))
          {
            delegate = sw;
            break;
          }
        }
        registry.put(ePackage, delegate);
      }
      return delegate;
    }
  }

  @Override
  public boolean isSwitchFor(EPackage ePackage)
  {
    return findDelegate(ePackage) != null;
  }
}
