/**
 * Copyright (c) 2009 BestSolution and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *   Tom Schindl - Initial API and implementation
 */
package org.eclipse.emf.test.databinding.edit;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.List;

import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.list.ListDiff;
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;
import org.eclipse.core.databinding.observable.set.SetDiff;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.databinding.IEMFListProperty;
import org.eclipse.emf.databinding.IEMFListProperty.ListElementAccess;
import org.eclipse.emf.databinding.edit.EMFEditProperties;
import org.eclipse.emf.databinding.edit.IEMFEditListProperty;
import org.eclipse.emf.databinding.edit.IEMFEditSetProperty;
import org.eclipse.emf.databinding.edit.IEMFEditValueProperty;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.test.common.TestUtil;
import org.eclipse.emf.test.databinding.emfdb.A;
import org.eclipse.emf.test.databinding.emfdb.B;
import org.eclipse.emf.test.databinding.emfdb.EmfdbFactory;
import org.eclipse.emf.test.databinding.emfdb.EmfdbPackage;
import org.junit.Before;
import org.junit.Test;


public class EMFEditPropertiesTest
{
  private Resource resource;

  private EditingDomain editingDomain;

  private Realm testRealm;

  private boolean flag;

  private ListDiffEntry[] listEntries;

  private ListDiff listDiff;

  private SetDiff diff;

  private BasicCommandStack commandStack;

  @Before
  public void setUp() throws Exception
  {
    ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
    ResourceSet resourceSet = new ResourceSetImpl();
    commandStack = new BasicCommandStack();

    resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
    editingDomain = new AdapterFactoryEditingDomain(adapterFactory, commandStack, resourceSet);
    URI uri = URI.createFileURI(TestUtil.getPluginDirectory("org.eclipse.emf.test.databinding") + "/model/A.xmi");
    resource = resourceSet.getResource(uri, true);
    testRealm = new Realm()
      {

        @Override
        public boolean isCurrent()
        {
          return true;
        }
      };
    flag = false;
    listEntries = null;
  }

  //  public void testValueEditingDomainEStructuralFeature()
  //  {
  //    fail("Not yet implemented");
  //  }
  //
  //  public void testValueEditingDomainFeaturePath()
  //  {
  //    fail("Not yet implemented");
  //  }
  //
  //  public void testValuesEditingDomainEStructuralFeatureArray()
  //  {
  //    fail("Not yet implemented");
  //  }
  //
  //  public void testValuesEditingDomainFeaturePathArray()
  //  {
  //    fail("Not yet implemented");
  //  }

  @Test
  public void testListEditingDomainEStructuralFeature()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          _testListEditingDomainEStructuralFeature();
        }
      });
  }

  public void _testListEditingDomainEStructuralFeature()
  {
    A a = (A)resource.getContents().get(0);
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__BLIST);
    IObservableList list = prop.observe(a);
    list.addListChangeListener(new IListChangeListener()
      {
        public void handleListChange(ListChangeEvent event)
        {
          flag = true;
          listEntries = event.diff.getDifferences();
        }
      });
    assertEquals(a.getBlist().size(), list.size());
    B b = EmfdbFactory.eINSTANCE.createB();
    list.add(b);
    // a.getBlist().add(b);
    assertEquals(a.getBlist().size(), list.size());
    assertTrue(flag);
    assertNotNull(listEntries);
    assertEquals(1, listEntries.length);
    assertTrue(listEntries[0].isAddition());
    assertSame(b, listEntries[0].getElement());
    assertEquals(a.getBlist().size() - 1, listEntries[0].getPosition());
    assertEquals(list.get(0), a.getBlist().get(0));
  }

  @Test
  public void testSetProperty()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {

        public void run()
        {
          _testSetProperty();
        }
      });
  }

  public void _testSetProperty()
  {
    A a = (A)resource.getContents().get(0);
    IEMFEditSetProperty prop = EMFEditProperties.set(editingDomain, EmfdbPackage.Literals.A__BLIST);
    IObservableSet set = prop.observe(a);
    assertNotNull(set);
    set.addSetChangeListener(new ISetChangeListener()
      {
        public void handleSetChange(SetChangeEvent event)
        {
          diff = event.diff;
        }
      });
    assertNull(diff);
    assertFalse(commandStack.canUndo());
    B b = EmfdbFactory.eINSTANCE.createB();
    // a.getBlist().add(b);
    set.add(b);
    assertNotNull(diff);
    assertEquals(1, diff.getAdditions().size());
    assertSame(b, diff.getAdditions().iterator().next());
    assertTrue(commandStack.canUndo());
    assertFalse(commandStack.canRedo());
    diff = null;
    commandStack.undo();
    assertNotNull(diff);
    assertEquals(1, diff.getRemovals().size());
    assertSame(b, diff.getRemovals().iterator().next());
    assertFalse(commandStack.canUndo());
    assertTrue(commandStack.canRedo());
  }

  //  public void testListEditingDomainFeaturePath()
  //  {
  //    fail("Not yet implemented");
  //  }
  //
  //  public void testMultiListEditingDomainEStructuralFeatureArray()
  //  {
  //    fail("Not yet implemented");
  //  }
  @Test
  public void testMultiListEditingDomainEStructuralFeatureArray()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          A a = (A)resource.getContents().get(0);
          IEMFEditListProperty prop = EMFEditProperties.multiList(editingDomain, EmfdbPackage.Literals.A__STRING, EmfdbPackage.Literals.A__BLIST);
          IObservableList list = prop.observe(a);
          list.addListChangeListener(new IListChangeListener()
            {

              public void handleListChange(ListChangeEvent event)
              {
                flag = true;
                listEntries = event.diff.getDifferences();
              }
            });
          assertEquals(a.getString(), list.get(0));
          assertEquals(a.getBlist().size() + 1, list.size());
          B b = EmfdbFactory.eINSTANCE.createB();
          a.getBlist().add(b);
          assertEquals(a.getBlist().size() + 1, list.size());
          assertTrue(flag);
          assertNotNull(listEntries);
          assertEquals(1, listEntries.length);
          assertTrue(listEntries[0].isAddition());
          assertSame(b, listEntries[0].getElement());
          assertEquals(a.getBlist().size(), listEntries[0].getPosition());
          assertEquals(list.get(1), a.getBlist().get(0));
          a.setString(null);
          assertEquals(a.getBlist().size(), list.size());
          assertEquals(list.get(0), a.getBlist().get(0));
        }
      });
  }

  @Test
  public void testResourceProperty()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {

        public void run()
        {
          _testResourceProperty();
        }
      });
  }

  private void _testResourceProperty()
  {
    IEMFEditListProperty prop = EMFEditProperties.resource(editingDomain);
    IObservableList list = prop.observe(resource);
    list.addListChangeListener(new IListChangeListener()
      {

        public void handleListChange(ListChangeEvent event)
        {
          listDiff = event.diff;
        }
      });
    assertNull(listDiff);

    // Adding
    A a = EmfdbFactory.eINSTANCE.createA();
    A a1 = EmfdbFactory.eINSTANCE.createA();

    resource.getContents().add(a);
    assertNotNull(listDiff);
    assertEquals(1, listDiff.getDifferences().length);
    assertSame(a, listDiff.getDifferences()[0].getElement());
    assertEquals(1, listDiff.getDifferences()[0].getPosition());
    assertTrue(listDiff.getDifferences()[0].isAddition());

    // Moving
    resource.getContents().move(0, a);
    assertEquals(2, listDiff.getDifferences().length);
    assertFalse(listDiff.getDifferences()[0].isAddition()); // Removal
    assertEquals(1, listDiff.getDifferences()[0].getPosition());
    assertTrue(listDiff.getDifferences()[1].isAddition()); // Addition
    assertEquals(0, listDiff.getDifferences()[1].getPosition());

    // Removing
    resource.getContents().remove(a);
    assertEquals(1, listDiff.getDifferences().length);
    assertFalse(listDiff.getDifferences()[0].isAddition()); // Removal
    assertEquals(0, listDiff.getDifferences()[0].getPosition());

    // Adding many
    resource.getContents().addAll(Arrays.asList(a, a1));
    assertEquals(2, listDiff.getDifferences().length);

    // Remove many
    resource.getContents().removeAll(Arrays.asList(a, a1));
    assertEquals(2, listDiff.getDifferences().length);

    // =============================================================

    // Changed through IObservableList
    // Add
    assertNull(commandStack.getUndoCommand());
    list.add(a);
    assertNotNull(listDiff);
    assertEquals(1, listDiff.getDifferences().length);
    assertSame(a, listDiff.getDifferences()[0].getElement());
    assertEquals(1, listDiff.getDifferences()[0].getPosition());
    assertTrue(listDiff.getDifferences()[0].isAddition());
    assertNotNull(commandStack.getUndoCommand());
    assertTrue(commandStack.canUndo());
    assertTrue(commandStack.getUndoCommand() instanceof CompoundCommand);
    CompoundCommand cmd = (CompoundCommand)commandStack.getUndoCommand();
    assertEquals(1, cmd.getCommandList().size());
    assertTrue(cmd.getCommandList().get(0) instanceof AddCommand);
    commandStack.undo();
    assertFalse(commandStack.canUndo());
    commandStack.redo();
    commandStack.flush();

    // Moving
    list.move(1, 0);
    assertEquals(2, listDiff.getDifferences().length);
    assertFalse(listDiff.getDifferences()[0].isAddition()); // Removal
    assertEquals(1, listDiff.getDifferences()[0].getPosition());
    assertTrue(listDiff.getDifferences()[1].isAddition()); // Addition
    assertEquals(0, listDiff.getDifferences()[1].getPosition());
    assertTrue(commandStack.canUndo());
    assertTrue(commandStack.getUndoCommand() instanceof CompoundCommand);
    commandStack.undo();
    assertFalse(commandStack.canUndo());
    commandStack.redo();
    commandStack.flush();

    // Removing
    list.remove(a);
    assertEquals(1, listDiff.getDifferences().length);
    assertFalse(listDiff.getDifferences()[0].isAddition()); // Removal
    assertEquals(0, listDiff.getDifferences()[0].getPosition());
    assertTrue(commandStack.canUndo());
    assertTrue(commandStack.getUndoCommand() instanceof CompoundCommand);
    commandStack.undo();
    assertFalse(commandStack.canUndo());
    commandStack.redo();
    commandStack.flush();

    // Adding many
    list.addAll(Arrays.asList(a, a1));
    assertEquals(2, listDiff.getDifferences().length);
    assertTrue(commandStack.canUndo());
    assertTrue(commandStack.getUndoCommand() instanceof CompoundCommand);
    commandStack.undo();
    assertFalse(commandStack.canUndo());
    commandStack.redo();
    commandStack.flush();

    // Remove many
    list.removeAll(Arrays.asList(a, a1));
    assertEquals(2, listDiff.getDifferences().length);
    assertTrue(commandStack.canUndo());
    assertTrue(commandStack.getUndoCommand() instanceof CompoundCommand);
    commandStack.undo();
    assertFalse(commandStack.canUndo());
    commandStack.redo();
    commandStack.flush();
  }

  @Test
  public void testListPropertyOnSingleFeature()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {

        public void run()
        {
          _testListPropertyOnSingleFeature();
        }
      });
  }

  public void _testListPropertyOnSingleFeature()
  {
    A a = (A)resource.getContents().get(0);
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__STRING);
    IObservableList l = prop.observe(a);
    assertEquals(1, l.size());
    assertEquals("Instance 1", l.get(0));
    a.setString("Bla");
    assertEquals("Bla", l.get(0));
    l.addListChangeListener(new IListChangeListener()
      {

        public void handleListChange(ListChangeEvent event)
        {
          assertEquals(2, event.diff.getDifferences().length);

          assertEquals(0, event.diff.getDifferences()[0].getPosition());
          assertFalse(event.diff.getDifferences()[0].isAddition());
          assertEquals("Bla", event.diff.getDifferences()[0].getElement());

          assertEquals(0, event.diff.getDifferences()[1].getPosition());
          assertTrue(event.diff.getDifferences()[1].isAddition());
          assertEquals("Instance 1", event.diff.getDifferences()[1].getElement());
        }
      });
    a.setString("Instance 1");
  }

  @Test
  public void testListElementProperty()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {

        public void run()
        {
          _testListElementProperty();
        }
      });
  }

  public void _testListElementProperty()
  {
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__BLIST);
    IEMFEditValueProperty valueProp = prop.value(new ListElementAccess<Object>()
      {

        @Override
        public int getReadValueIndex(List<Object> list)
        {
          return 0;
        }

        @Override
        public int getWriteValueIndex(List<Object> list)
        {
          return 0;
        }
      });

    IEMFEditValueProperty detailValue = valueProp.value(EmfdbPackage.Literals.B__STRING);

    IObservableValue value = detailValue.observe(resource.getContents().get(0));
    assertEquals("Instance 1", value.getValue());
    A a = (A)resource.getContents().get(0);
    a.getBlist().get(0).setString("Bla Bla");
    assertEquals("Bla Bla", value.getValue());

    B b = EmfdbFactory.eINSTANCE.createB();
    b.setString("New Element");

    a.getBlist().add(0, b);
    assertEquals("New Element", value.getValue());

    b = EmfdbFactory.eINSTANCE.createB();
    b.setString("New Element 2");
    IObservableValue aObservable = valueProp.observe(a);
    try
    {
      aObservable.setValue(b);
    }
    finally
    {
      aObservable.dispose();
    }
    assertEquals("New Element 2", value.getValue());
  }

  @Test
  public void test_attributeListWithDuplicates()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          _test_attributeListWithDuplicates();
        }
      });
  }

  public void _test_attributeListWithDuplicates()
  {
    A a = (A)resource.getContents().get(0);
    EList<String> strings = a.getStrings();

    IEMFListProperty stringsListProperty = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__STRINGS);
    IObservableList observableList = stringsListProperty.observe(a);
    EList<String> normalList = new BasicEList<String>();

    String value1 = "value1";
    String value1Copy = new String(value1);
    String value2 = "value2";
    String value3 = "value3";

    observableList.add(value1);
    observableList.add(value2);
    observableList.add(value1Copy);
    observableList.add(3, value2);

    normalList.add(value1);
    normalList.add(value2);
    normalList.add(value1Copy);
    normalList.add(3, value2);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    observableList.set(1, value3);
    normalList.set(1, value3);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    observableList.remove(2);
    normalList.remove(2);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    observableList.add(value1);
    normalList.add(value1);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    observableList.remove(observableList.size() - 1);
    normalList.remove(normalList.size() - 1);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=478053 that 
    //  EList.move(int, int) and 
    //  IObservableList.move(int, int) 
    // have opposite meaning for source and target.
    //
    observableList.move(2, 0);
    normalList.move(0, 2);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    observableList.add(value1);
    normalList.add(value1);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);

    observableList.move(observableList.size() - 1, 0);
    normalList.move(0, normalList.size() - 1);

    assertSameListContents(normalList, observableList);
    assertSameListContents(normalList, strings);
  }

  private void assertSameListContents(List<?> list1, List<?> list2)
  {
    String string1 = list1.toString();
    String string2 = list2.toString();
    assertEquals(string1, string2);
    assertEquals(list1.size(), list2.size());
    for (int i = 0, size = list1.size(); i < size; ++i)
    {
      assertSame("Same object expected at index " + i + " of " + string1, list1.get(i), list2.get(i));
    }
  }

  @Test
  public void testMultiValued_NotUnique_Replace_PrimitiveAttributes()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          _testMultiValued_NotUnique_Replace_PrimitiveAttributes();
        }
      });
  }

  private void _testMultiValued_NotUnique_Replace_PrimitiveAttributes()
  {
    A a = (A)resource.getContents().get(0);
    a.getNotUniqueValues().clear();
    Double d20 = 20.0;
    Double d30 = 30.0;
    a.getNotUniqueValues().add(d20);
    a.getNotUniqueValues().add(d30);
    a.getNotUniqueValues().add(d20);
    a.getNotUniqueValues().add(d30);
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__NOT_UNIQUE_VALUES);
    IObservableList list = prop.observe(a);
    list.set(1, 100.0);
    assertEquals(100.0, list.get(1));
  }

  @Test
  public void testMultiValued_Unique_Replace_PrimitiveAttributes()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          _testMultiValued_Unique_Replace_PrimitiveAttributes();
        }
      });
  }

  private void _testMultiValued_Unique_Replace_PrimitiveAttributes()
  {
    A a = (A)resource.getContents().get(0);
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__PRIMITIVE_VALUES);
    IObservableList list = prop.observe(a);
    assertEquals(30.0, list.get(1));
    list.set(1, 100.0);
    assertEquals(100.0, list.get(1));
  }

  public void testMultiValued_NotUnique_Remove_PrimitiveAttributes()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          _testMultiValued_NotUnique_Remove_PrimitiveAttributes();
        }
      });
  }

  private void _testMultiValued_NotUnique_Remove_PrimitiveAttributes()
  {
    A a = (A)resource.getContents().get(0);
    a.getNotUniqueValues().clear();
    Double d20 = 20.0;
    Double d30 = 30.0;
    a.getNotUniqueValues().add(d20);
    a.getNotUniqueValues().add(d30);
    a.getNotUniqueValues().add(d20);
    a.getNotUniqueValues().add(d30);
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__NOT_UNIQUE_VALUES);
    IObservableList list = prop.observe(a);
    list.remove(3);
    assertEquals(20.0, list.get(0));
    assertEquals(30.0, list.get(1));
    assertEquals(20.0, list.get(2));
  }

  public void testMultiValued_Unique_Remove_PrimitiveAttributes()
  {
    Realm.runWithDefault(testRealm, new Runnable()
      {
        public void run()
        {
          _testMultiValued_Unique_Remove_PrimitiveAttributes();
        }
      });
  }

  private void _testMultiValued_Unique_Remove_PrimitiveAttributes()
  {
    A a = (A)resource.getContents().get(0);
    IEMFEditListProperty prop = EMFEditProperties.list(editingDomain, EmfdbPackage.Literals.A__PRIMITIVE_VALUES);
    IObservableList list = prop.observe(a);
    list.remove(1);
    assertEquals(1, list.size());
    assertEquals(20.0, list.get(0));
  }
}
