/**
 * Copyright (c) 2005 IBM Corporation 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:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.test.core.ecore;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.util.FeatureMap;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.junit.Test;


public class FeatureMapTest
{
  @Test
  public void testAttributesAsFeatures() throws Exception
  {
    EcorePackage ecorePackage = EcorePackage.eINSTANCE;
    EcoreFactory ecoreFactory = EcoreFactory.eINSTANCE;

    EPackage myPack = ecoreFactory.createEPackage();
    myPack.setName("mypack");

    EClass employeeClass = ecoreFactory.createEClass();
    myPack.getEClassifiers().add(employeeClass);
    employeeClass.setName("Employee");

    //   Sequence
    EAttribute ordersAttr = ecoreFactory.createEAttribute();
    employeeClass.getEStructuralFeatures().add(ordersAttr);
    ordersAttr.setName("orders");
    // set the multiplicity
    ordersAttr.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    // use the EFeatureMapEntry type
    ordersAttr.setEType(ecorePackage.getEFeatureMapEntry());
    // use the default ExtendedMetadata instance to set the feature to the desired kind
    ExtendedMetaData.INSTANCE.setFeatureKind(ordersAttr, ExtendedMetaData.GROUP_FEATURE);

    // the first derived reference
    EAttribute preferredAtt = ecoreFactory.createEAttribute();
    employeeClass.getEStructuralFeatures().add(preferredAtt);
    preferredAtt.setName("preferredOrders");
    preferredAtt.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    preferredAtt.setEType(ecorePackage.getEString());
    preferredAtt.setVolatile(true);
    preferredAtt.setTransient(true);
    preferredAtt.setDerived(true);
    ExtendedMetaData.INSTANCE.setGroup(preferredAtt, ordersAttr);

    //  the second derived reference
    EAttribute standardAtt = ecoreFactory.createEAttribute();
    employeeClass.getEStructuralFeatures().add(standardAtt);
    standardAtt.setName("standardOrders");
    standardAtt.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    standardAtt.setEType(ecorePackage.getEString());
    standardAtt.setVolatile(true);
    standardAtt.setTransient(true);
    standardAtt.setDerived(true);
    ExtendedMetaData.INSTANCE.setGroup(standardAtt, ordersAttr);

    assertTrue(FeatureMapUtil.isFeatureMap(ordersAttr));
    assertFalse(FeatureMapUtil.isFeatureMap(preferredAtt));
    assertFalse(FeatureMapUtil.isFeatureMap(standardAtt));

    EObject eObject = myPack.getEFactoryInstance().create(employeeClass);
    @SuppressWarnings("unchecked")
    List<String> preferredList = (List<String>)eObject.eGet(preferredAtt);
    @SuppressWarnings("unchecked")
    List<String> standardList = (List<String>)eObject.eGet(standardAtt);

    preferredList.add("String1");
    preferredList.add("String2");
    standardList.add("String3");
    standardList.add("String4");

    assertEquals("String1", preferredList.get(0));
    assertEquals("String2", preferredList.get(1));
    assertEquals("String3", standardList.get(0));
    assertEquals("String4", standardList.get(1));

    FeatureMap ordersFeatureMap = (FeatureMap)eObject.eGet(ordersAttr);
    assertEquals(preferredList, ordersFeatureMap.get(preferredAtt, false));
    assertEquals(standardList, ordersFeatureMap.get(standardAtt, false));

    ordersFeatureMap.add(preferredAtt, "String5");
    ordersFeatureMap.add(standardAtt, "String6");
    assertEquals("String5", preferredList.get(2));
    assertEquals("String6", standardList.get(2));
  }

  @Test
  public void testReferencesAsFeatures() throws Exception
  {
    EcorePackage ecorePackage = EcorePackage.eINSTANCE;
    EcoreFactory ecoreFactory = EcoreFactory.eINSTANCE;

    EPackage myPack = ecoreFactory.createEPackage();
    myPack.setName("mypack");

    EClass employeeClass = ecoreFactory.createEClass();
    myPack.getEClassifiers().add(employeeClass);
    employeeClass.setName("Employee");

    EClass orderClass = ecoreFactory.createEClass();
    myPack.getEClassifiers().add(orderClass);
    orderClass.setName("Order");

    //   Sequence
    EAttribute ordersAttr = ecoreFactory.createEAttribute();
    employeeClass.getEStructuralFeatures().add(ordersAttr);
    ordersAttr.setName("orders");
    ordersAttr.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    ordersAttr.setEType(ecorePackage.getEFeatureMapEntry());
    ExtendedMetaData.INSTANCE.setFeatureKind(ordersAttr, ExtendedMetaData.GROUP_FEATURE);

    // the first derived reference
    EReference preferredRef = ecoreFactory.createEReference();
    employeeClass.getEStructuralFeatures().add(preferredRef);
    preferredRef.setName("preferredOrders");
    preferredRef.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    preferredRef.setContainment(true);
    preferredRef.setEType(orderClass);
    preferredRef.setVolatile(true);
    preferredRef.setTransient(true);
    preferredRef.setDerived(true);
    ExtendedMetaData.INSTANCE.setGroup(preferredRef, ordersAttr);

    //  the second derived reference
    EReference standardRef = ecoreFactory.createEReference();
    employeeClass.getEStructuralFeatures().add(standardRef);
    standardRef.setName("standardOrders");
    standardRef.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    standardRef.setEType(orderClass);
    standardRef.setContainment(true);
    standardRef.setVolatile(true);
    standardRef.setTransient(true);
    standardRef.setDerived(true);
    ExtendedMetaData.INSTANCE.setGroup(standardRef, ordersAttr);

    EObject eObject = myPack.getEFactoryInstance().create(employeeClass);
    @SuppressWarnings("unchecked")
    List<EObject> preferredList = (List<EObject>)eObject.eGet(preferredRef);
    @SuppressWarnings("unchecked")
    List<EObject> standardList = (List<EObject>)eObject.eGet(standardRef);

    EObject order1 = myPack.getEFactoryInstance().create(orderClass);
    EObject order2 = myPack.getEFactoryInstance().create(orderClass);
    EObject order3 = myPack.getEFactoryInstance().create(orderClass);
    EObject order4 = myPack.getEFactoryInstance().create(orderClass);

    preferredList.add(order1);
    preferredList.add(order2);
    standardList.add(order3);
    standardList.add(order4);

    assertEquals(order1, preferredList.get(0));
    assertEquals(order2, preferredList.get(1));
    assertEquals(order3, standardList.get(0));
    assertEquals(order4, standardList.get(1));

    FeatureMap ordersFeatureMap = (FeatureMap)eObject.eGet(ordersAttr);
    assertEquals(preferredList, ordersFeatureMap.get(preferredRef, false));
    assertEquals(standardList, ordersFeatureMap.get(standardRef, false));

    EObject order5 = myPack.getEFactoryInstance().create(orderClass);
    EObject order6 = myPack.getEFactoryInstance().create(orderClass);

    ordersFeatureMap.add(preferredRef, order5);
    ordersFeatureMap.add(standardRef, order6);
    assertEquals(order5, preferredList.get(2));
    assertEquals(order6, standardList.get(2));

    assertEquals(3, preferredList.size());
    assertEquals(3, standardList.size());
    ordersFeatureMap.add(preferredRef, order3);
    assertEquals(4, preferredList.size());
    assertEquals(order3, preferredList.get(3));
    assertEquals(2, standardList.size());
    assertFalse(standardList.contains(order3));
  }

  /*
   * Bugzilla 127109
   */
  @Test
  public void testMoveElements() throws Exception
  {
    moveElementsTest(false);
  }

  /*
   * Bugzilla 127109
   */
  @Test
  public void testMoveElements_WithNotification() throws Exception
  {
    moveElementsTest(true);
  }

  public void moveElementsTest(boolean withNotification) throws Exception
  {
    EPackage pack = EcoreFactory.eINSTANCE.createEPackage();
    pack.setName("pack");
    pack.setNsURI("featureMap.test.pack");

    EClass house = EcoreFactory.eINSTANCE.createEClass();
    pack.getEClassifiers().add(house);
    house.setName("Order");

    EClass car = EcoreFactory.eINSTANCE.createEClass();
    pack.getEClassifiers().add(car);
    car.setName("Car");

    EClass person = EcoreFactory.eINSTANCE.createEClass();
    pack.getEClassifiers().add(person);
    person.setName("Person");

    EAttribute things = EcoreFactory.eINSTANCE.createEAttribute();
    person.getEStructuralFeatures().add(things);
    things.setName("things");
    things.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    things.setEType(EcorePackage.Literals.EFEATURE_MAP_ENTRY);
    ExtendedMetaData.INSTANCE.setFeatureKind(things, ExtendedMetaData.GROUP_FEATURE);

    EReference houses = EcoreFactory.eINSTANCE.createEReference();
    person.getEStructuralFeatures().add(houses);
    houses.setName("houses");
    houses.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    houses.setContainment(true);
    houses.setEType(house);
    houses.setVolatile(true);
    houses.setTransient(true);
    houses.setDerived(true);
    ExtendedMetaData.INSTANCE.setGroup(houses, things);

    EReference cars = EcoreFactory.eINSTANCE.createEReference();
    person.getEStructuralFeatures().add(cars);
    cars.setName("cars");
    cars.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    cars.setContainment(true);
    cars.setEType(car);
    cars.setVolatile(true);
    cars.setTransient(true);
    cars.setDerived(true);
    ExtendedMetaData.INSTANCE.setGroup(cars, things);

    EObject john = EcoreUtil.create(person);
    EObject house1 = EcoreUtil.create(house);
    EObject car1 = EcoreUtil.create(car);

    FeatureMap thingsFeatureMap = (FeatureMap)john.eGet(things);

    final List<Adapter> referenceNotificationCount = new ArrayList<Adapter>();
    final List<Adapter> attributeNotificationCount = new ArrayList<Adapter>();
    final Map<EObject, EReference> referenceByValueMap = new LinkedHashMap<EObject, EReference>();
    if (withNotification)
    {
      final FeatureMap theFeatureMap = thingsFeatureMap;
      Adapter adapter = new AdapterImpl()
        {
          @Override
          public void notifyChanged(Notification msg)
          {
            assertEquals(referenceByValueMap.size(), theFeatureMap.size());

            int valueCount = 0;
            int index=0;
            for (Map.Entry<?, ?> entry  : referenceByValueMap.entrySet())
            {
              assertEquals(entry.getValue(), theFeatureMap.getEStructuralFeature(index));
              assertEquals(entry.getKey(), theFeatureMap.getValue(index));

              ++index;
              if (entry.getValue() == msg.getFeature())
              {
                valueCount++;
              }
            }

            if (msg.getFeature() instanceof EReference)
            {
              referenceNotificationCount.add(this);
              if (msg.getEventType() == Notification.MOVE)
              {
                assertTrue("pos: " + msg.getPosition(), msg.getPosition() >= 0);
                assertTrue("pos: " + msg.getPosition() + " vc:" + valueCount, msg.getPosition() < valueCount);
              }
            }
            else if (msg.getFeature() instanceof EAttribute)
            {
              attributeNotificationCount.add(this);
            }
          }
        };
      john.eAdapters().add(adapter);
    }

    if (withNotification) referenceByValueMap.put(house1, houses);
    @SuppressWarnings("unchecked")
    List<EObject> johnHouses = ((List<EObject>)john.eGet(houses));
    johnHouses.add(house1);
    if (withNotification) assertEquals(1, referenceNotificationCount.size());
    if (withNotification) assertEquals(1, attributeNotificationCount.size());
    if (withNotification) referenceByValueMap.put(car1, cars);
    @SuppressWarnings("unchecked")
    List<EObject> johnCars = ((List<EObject>)john.eGet(cars));
    johnCars.add(car1);
    if (withNotification) assertEquals(2, referenceNotificationCount.size());
    if (withNotification) assertEquals(2, attributeNotificationCount.size());

    assertEquals(2, thingsFeatureMap.size());
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(house1, thingsFeatureMap.getValue(0));
    assertEquals(car1, thingsFeatureMap.getValue(1));

    // Target > Source && Different References
    if (withNotification) referenceByValueMap.clear();
    if (withNotification) referenceByValueMap.put(car1, cars);
    if (withNotification) referenceByValueMap.put(house1, houses);
    thingsFeatureMap.move(1, 0);
    if (withNotification) assertEquals(2, referenceNotificationCount.size());
    if (withNotification) assertEquals(3, attributeNotificationCount.size());

    assertEquals(2, thingsFeatureMap.size());
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(car1, thingsFeatureMap.getValue(0));
    assertEquals(house1, thingsFeatureMap.getValue(1));

    EObject car2 = EcoreUtil.create(car);
    if (withNotification) referenceByValueMap.clear();
    if (withNotification) referenceByValueMap.put(car1, cars);
    if (withNotification) referenceByValueMap.put(car2, cars);
    if (withNotification) referenceByValueMap.put(house1, houses);
    thingsFeatureMap.add(1, cars, car2);
    if (withNotification) assertEquals(3, referenceNotificationCount.size());
    if (withNotification) assertEquals(4, attributeNotificationCount.size());

    assertEquals(3, thingsFeatureMap.size());
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(2));
    assertEquals(car1, thingsFeatureMap.getValue(0));
    assertEquals(car2, thingsFeatureMap.getValue(1));
    assertEquals(house1, thingsFeatureMap.getValue(2));

    //  Target < Source && Different References
    if (withNotification) referenceByValueMap.clear();
    if (withNotification) referenceByValueMap.put(house1, houses);
    if (withNotification) referenceByValueMap.put(car1, cars);
    if (withNotification) referenceByValueMap.put(car2, cars);
    thingsFeatureMap.move(0, 2);
    if (withNotification) assertEquals(3, referenceNotificationCount.size());
    if (withNotification) assertEquals(5, attributeNotificationCount.size());

    assertEquals(3, thingsFeatureMap.size());
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(2));
    assertEquals(house1, thingsFeatureMap.getValue(0));
    assertEquals(car1, thingsFeatureMap.getValue(1));
    assertEquals(car2, thingsFeatureMap.getValue(2));

    //  Target < Source && Same Reference
    if (withNotification) referenceByValueMap.clear();
    if (withNotification) referenceByValueMap.put(house1, houses);
    if (withNotification) referenceByValueMap.put(car2, cars);
    if (withNotification) referenceByValueMap.put(car1, cars);
    thingsFeatureMap.move(1, 2);
    if (withNotification) assertEquals(4, referenceNotificationCount.size());
    if (withNotification) assertEquals(6, attributeNotificationCount.size());

    assertEquals(3, thingsFeatureMap.size());
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(2));
    assertEquals(house1, thingsFeatureMap.getValue(0));
    assertEquals(car2, thingsFeatureMap.getValue(1));
    assertEquals(car1, thingsFeatureMap.getValue(2));

    //  Target > Source && Same Reference
    if (withNotification) referenceByValueMap.clear();
    if (withNotification) referenceByValueMap.put(house1, houses);
    if (withNotification) referenceByValueMap.put(car1, cars);
    if (withNotification) referenceByValueMap.put(car2, cars);
    thingsFeatureMap.move(2, 1);
    if (withNotification) assertEquals(5, referenceNotificationCount.size());
    if (withNotification) assertEquals(7, attributeNotificationCount.size());

    assertEquals(3, thingsFeatureMap.size());
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(2));
    assertEquals(house1, thingsFeatureMap.getValue(0));
    assertEquals(car1, thingsFeatureMap.getValue(1));
    assertEquals(car2, thingsFeatureMap.getValue(2));

    //  Target == Source
    thingsFeatureMap.move(2, 2);
    if (withNotification) assertEquals(5, referenceNotificationCount.size());
    if (withNotification) assertEquals(8, attributeNotificationCount.size());

    assertEquals(3, thingsFeatureMap.size());
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(2));
    assertEquals(house1, thingsFeatureMap.getValue(0));
    assertEquals(car1, thingsFeatureMap.getValue(1));
    assertEquals(car2, thingsFeatureMap.getValue(2));

    //  Target < Source && Different References && Source Reference changes
    if (withNotification) referenceByValueMap.clear();
    if (withNotification) referenceByValueMap.put(car2, cars);
    if (withNotification) referenceByValueMap.put(house1, houses);
    if (withNotification) referenceByValueMap.put(car1, cars);
    thingsFeatureMap.move(0, 2);
    if (withNotification) assertEquals(6, referenceNotificationCount.size());
    if (withNotification) assertEquals(9, attributeNotificationCount.size());

    assertEquals(3, thingsFeatureMap.size());
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(0));
    assertEquals(houses, thingsFeatureMap.getEStructuralFeature(1));
    assertEquals(cars, thingsFeatureMap.getEStructuralFeature(2));
    assertEquals(car2, thingsFeatureMap.getValue(0));
    assertEquals(house1, thingsFeatureMap.getValue(1));
    assertEquals(car1, thingsFeatureMap.getValue(2));

    if (withNotification)
    {
      assertFalse(referenceByValueMap.isEmpty());
      assertFalse(referenceNotificationCount.isEmpty());
      assertFalse(attributeNotificationCount.isEmpty());
    }
    else
    {
      assertTrue(referenceByValueMap.isEmpty());
      assertTrue(referenceNotificationCount.isEmpty());
      assertTrue(attributeNotificationCount.isEmpty());
    }
  }
}
