/**
 * Copyright (c) 2004 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.assertNull;
import static org.junit.Assert.assertTrue;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.URI;
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.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMLResourceImpl;
import org.junit.Before;
import org.junit.Test;

public class ResourceCacheMechanismTest
{
  private EAttribute id;
  private EAttribute name;

  private EObject john;
  private EObject mary;
  private EObject johnDoe;

  @Before
  public void setUp() throws Exception
  {
    EPackage pack = EcoreFactory.eINSTANCE.createEPackage();

    EClass employee = EcoreFactory.eINSTANCE.createEClass();
    employee.setName("Employee");
    pack.getEClassifiers().add(employee);

    id = EcoreFactory.eINSTANCE.createEAttribute();
    id.setName("id");
    id.setID(true);
    id.setEType(EcorePackage.Literals.ESTRING);
    employee.getEStructuralFeatures().add(id);

    name = EcoreFactory.eINSTANCE.createEAttribute();
    name.setName("name");
    name.setEType(EcorePackage.Literals.ESTRING);
    employee.getEStructuralFeatures().add(name);

    john = pack.getEFactoryInstance().create(employee);
    john.eSet(id, "1");
    john.eSet(name, "John");

    mary = pack.getEFactoryInstance().create(employee);
    mary.eSet(id, "2");
    mary.eSet(name, "mary");

    johnDoe = pack.getEFactoryInstance().create(employee);
    johnDoe.eSet(name, "John");
  }

  /*
   * Bugzilla 62885
   */
  @Test
  public void testResourceImplIntrinsicIDCache() throws Exception
  {
    Resource resource = new ResourceImpl();

    //Setting the map before adding the objects
    Map<String, EObject> map = new HashMap<String, EObject>();
    ((ResourceImpl)resource).setIntrinsicIDToEObjectMap(map);

    resource.getContents().add(john);
    resource.getContents().add(mary);
    resource.getContents().add(johnDoe);

    //The map should have all the objects with ID (john and mary)
    assertEquals(2, map.size());
    assertEquals(john, map.get(john.eGet(id)));
    assertEquals(mary, map.get("2"));

    resource.getContents().remove(mary);

    //Mary should have been removed from the map.
    assertEquals(1, map.size());
    assertEquals(john, map.get(john.eGet(id)));

    //Setting johnDoe's ID
    johnDoe.eSet(id, "newId");

    //The map should be the same as before
    assertEquals(1, map.size());
    assertEquals(john, map.get(john.eGet(id)));

    //Since john was added before johnDoe, retrieving john should not
    //add johnDoe to the map
    assertEquals(john, resource.getEObject("1"));
    assertEquals(1, map.size());
    assertEquals(john, map.get(john.eGet(id)));

    //Adding mary again but removing her ID first so she is not added to the map
    mary.eSet(id, null);
    resource.getContents().add(mary);
    assertEquals(1, map.size());
    assertEquals(john, map.get(john.eGet(id)));

    //Setting mary's ID
    mary.eSet(id, "2");

    //Now mary was added after johnDoe so retrieving Mary should add
    //johnDoe to the map
    assertEquals(mary, resource.getEObject("2"));
    assertEquals(3, map.size());
    assertEquals(john, map.get(john.eGet(id)));
    assertEquals(mary, map.get("2"));
    assertEquals(johnDoe, map.get(johnDoe.eGet(id)));
  }

  @Test
  public void testXMLResourceIDCache() throws Exception
  {
    XMLResource xmlResource = new XMLResourceImpl();

    //Setting an external ID for john
    xmlResource.setID(john, "externalIDForJohn");
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals("externalIDForJohn", xmlResource.getID(john));

    //Setting the id doesn't add the object to the resource
    assertTrue(xmlResource.getContents().isEmpty());

    xmlResource.getContents().add(john);
    xmlResource.getContents().add(mary);

    //Only john can be retrieved by an external ID
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));

    //Everybody can be retrieved by their IDs (XMLResource inherits this from Resource)
    assertEquals(john, xmlResource.getEObject((String)john.eGet(id)));
    assertEquals(mary, xmlResource.getEObject((String)mary.eGet(id)));

    //Setting mary's external ID
    xmlResource.setID(mary, "MARY");

    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals(mary, xmlResource.getEObject("MARY"));
    assertEquals("externalIDForJohn", xmlResource.getID(john));
    assertEquals("MARY", xmlResource.getID(mary));

    //john and mary can be retrieved by their external IDs
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals(mary, xmlResource.getEObject("MARY"));

    //Removing mary's external id
    xmlResource.setID(mary, null);

    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertNull(xmlResource.getEObject("MARY"));
    assertEquals("externalIDForJohn", xmlResource.getID(john));
    assertNull(xmlResource.getID(mary));

    //Setting mary's external ID again
    xmlResource.setID(mary, "MARY");
    //john and mary can be retrieved by their external IDs
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals(mary, xmlResource.getEObject("MARY"));

    //Removing mary from the resource
    xmlResource.getContents().remove(mary);
    assertEquals(1, xmlResource.getContents().size());

    //Mary should not be in the maps.
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals("externalIDForJohn", xmlResource.getID(john));
    assertNull(xmlResource.getEObject("MARY"));
    assertNull(xmlResource.getID(mary));
  }

  /*
   * IMPORTANT:  This is a test!!  The maps IDToEObject and EObjectToID should never
   * be used directly.  The get methods may be removed in future versions of EMF.
   */
  @Test
  public void testXMLResourceImplIDCache() throws Exception
  {
    XMLResource xmlResource = new XMLResourceImpl();

    //Setting an external ID for john
    xmlResource.setID(john, "externalIDForJohn");
    assertEquals(1, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().size());
    assertEquals(john, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().get("externalIDForJohn"));
    assertEquals(1, ((XMLResourceImpl)xmlResource).getEObjectToIDMap().size());
    assertEquals("externalIDForJohn", ((XMLResourceImpl)xmlResource).getEObjectToIDMap().get(john));

    //Setting the id doesn't add the object to the resource
    assertTrue(xmlResource.getContents().isEmpty());

    xmlResource.getContents().add(john);
    xmlResource.getContents().add(mary);
    xmlResource.getContents().add(johnDoe);

    //Only john can be retrieved by an external ID
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));

    //Everybody can be retrieved by their IDs (XMLResource inherits this from Resource)
    assertEquals(john, xmlResource.getEObject((String)john.eGet(id)));
    assertEquals(mary, xmlResource.getEObject((String)mary.eGet(id)));

    //Setting mary's external ID
    xmlResource.setID(mary, "MARY");

    assertEquals(2, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().size());
    assertEquals(john, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().get("externalIDForJohn"));
    assertEquals(mary, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().get("MARY"));
    assertEquals(2, ((XMLResourceImpl)xmlResource).getEObjectToIDMap().size());
    assertEquals("externalIDForJohn", ((XMLResourceImpl)xmlResource).getEObjectToIDMap().get(john));
    assertEquals("MARY", ((XMLResourceImpl)xmlResource).getEObjectToIDMap().get(mary));

    //john and mary can be retrieved by their external IDs
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals(mary, xmlResource.getEObject("MARY"));

    //Removing mary's external id
    xmlResource.setID(mary, null);

    assertEquals(1, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().size());
    assertEquals(john, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().get("externalIDForJohn"));
    assertNull(((XMLResourceImpl)xmlResource).getIDToEObjectMap().get("MARY"));
    assertEquals(1, ((XMLResourceImpl)xmlResource).getEObjectToIDMap().size());
    assertEquals("externalIDForJohn", ((XMLResourceImpl)xmlResource).getEObjectToIDMap().get(john));
    assertNull(((XMLResourceImpl)xmlResource).getEObjectToIDMap().get(mary));

    //Setting mary's external ID again
    xmlResource.setID(mary, "MARY");
    //john and mary can be retrieved by their external IDs
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals(mary, xmlResource.getEObject("MARY"));

    //Removing mary
    xmlResource.getContents().remove(mary);
    assertEquals(2, xmlResource.getContents().size());

    //Mary should not be in the maps.
    assertEquals(1, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().size());
    assertEquals(john, ((XMLResourceImpl)xmlResource).getIDToEObjectMap().get("externalIDForJohn"));
    assertEquals(1, ((XMLResourceImpl)xmlResource).getEObjectToIDMap().size());
    assertEquals("externalIDForJohn", ((XMLResourceImpl)xmlResource).getEObjectToIDMap().get(john));
  }

  @Test
  public void testAllIDCaches() throws Exception
  {
    XMLResource xmlResource = new XMLResourceImpl();

    //Setting an external ID for john
    xmlResource.setID(john, "externalIDForJohn");

    //Setting the map before adding the objects
    Map<String, EObject> map = new HashMap<String, EObject>();
    ((ResourceImpl)xmlResource).setIntrinsicIDToEObjectMap(map);

    xmlResource.getContents().add(john);
    xmlResource.getContents().add(mary);

    //Unsetting johnDoe's id
    johnDoe.eSet(id, null);
    xmlResource.getContents().add(johnDoe);

    //john with external ID, mary with instrinsic ID, johnDoe without any ID
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals("externalIDForJohn", xmlResource.getID(john));
    assertEquals(mary, xmlResource.getEObject((String)mary.eGet(id)));
    assertNull(xmlResource.getID(mary));
    assertNull(xmlResource.getID(johnDoe));
    assertEquals(2, map.size());
    assertEquals(john, map.get(john.eGet(id)));
    assertEquals(mary, map.get(mary.eGet(id)));

    // Unsetting john's ID
    xmlResource.setID(john, null);

    // john and mary with instrinsic IDs, johnDoe without any ID
    assertNull(xmlResource.getEObject("externalIDForJohn"));
    assertNull(xmlResource.getID(john));
    assertEquals(john, xmlResource.getEObject((String)john.eGet(id)));
    assertEquals(2, map.size());
    assertEquals(john, map.get(john.eGet(id)));
    assertEquals(mary, map.get(mary.eGet(id)));

    xmlResource.setID(john, "externalIDForJohn");

    //john with external ID, mary with instrinsic ID, johnDoe without any ID
    assertEquals(john, xmlResource.getEObject("externalIDForJohn"));
    assertEquals("externalIDForJohn", xmlResource.getID(john));
    assertEquals(mary, xmlResource.getEObject((String)mary.eGet(id)));
    assertNull(xmlResource.getID(mary));
    assertNull(xmlResource.getID(johnDoe));
    assertEquals(2, map.size());
    assertEquals(john, map.get(john.eGet(id)));
    assertEquals(mary, map.get(mary.eGet(id)));

    xmlResource.getContents().remove(john);

    assertNull(xmlResource.getEObject("externalIDForJohn"));
    assertNull(xmlResource.getID(john));
    assertNull(xmlResource.getEObject((String)john.eGet(id)));
    assertEquals(mary, xmlResource.getEObject((String)mary.eGet(id)));
    assertEquals(1, map.size());
    assertEquals(mary, map.get(mary.eGet(id)));
  }

  @Test
  public void testResourceSetURICache() throws Exception
  {
    ResourceSet resourceSet = new ResourceSetImpl();

    Resource resource1 = new ResourceImpl();
    resource1.setURI(URI.createFileURI("f1"));
    resource1.getContents().add(john);
    resource1.getContents().add(mary);

    Resource resource2 = new ResourceImpl();
    resource2.setURI(URI.createURI("http://www.eclipse.org/emf"));
    resource2.getContents().add(johnDoe);

    Map<URI, Resource> map = new HashMap<URI, Resource>();
    ((ResourceSetImpl)resourceSet).setURIResourceMap(map);

    resourceSet.getResources().add(resource1);
    resourceSet.getResources().add(resource2);

    assertTrue(map.isEmpty());

    assertEquals(resource1, resourceSet.getResource(URI.createFileURI("f1"), false));
    assertEquals(1, map.size());
    assertEquals(resource1, map.get(URI.createFileURI("f1")));
    assertEquals(john, resourceSet.getEObject(URI.createFileURI("f1").appendFragment(resource1.getURIFragment(john)), false));
    assertEquals(1, map.size());
    assertEquals(resource1, map.get(URI.createFileURI("f1")));

    assertEquals(johnDoe, resourceSet.getEObject(resource2.getURI().appendFragment(resource2.getURIFragment(johnDoe)), false));
    assertEquals(2, map.size());
    assertEquals(resource1, map.get(URI.createFileURI("f1")));
    assertEquals(resource2, map.get(resource2.getURI()));

    resourceSet.getResources().remove(resource1);

    assertEquals(1, map.size());
    assertEquals(resource2, map.get(resource2.getURI()));

    resourceSet.getResources().clear();
    assertTrue(map.isEmpty());
  }

  @Test
  public void testXMLResourceIDCacheWithContainment() throws Exception
  {
    EClass person = john.eClass();
    EPackage pack = person.getEPackage();

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

    EReference employees = EcoreFactory.eINSTANCE.createEReference();
    company.getEStructuralFeatures().add(employees);
    employees.setName("employees");
    employees.setContainment(true);
    employees.setUpperBound(ETypedElement.UNBOUNDED_MULTIPLICITY);
    employees.setEType(person);

    EObject company1 = pack.getEFactoryInstance().create(company);
    @SuppressWarnings("unchecked")
    List<EObject> company1Employees = ((List<EObject>)company1.eGet(employees));
    company1Employees.add(john);
    assertEquals(company1, john.eContainer());

    XMLResource xmlResource = new XMLResourceImpl();
    xmlResource.getContents().add(company1);
    assertEquals(xmlResource, company1.eResource());
    assertEquals(john, xmlResource.getEObject((String)john.eGet(id)));

    xmlResource.setID(john, "JOHN");
    assertEquals(john, xmlResource.getEObject("JOHN"));

    EObject company2 = pack.getEFactoryInstance().create(company);
    @SuppressWarnings("unchecked")
    List<EObject> company2Employees = ((List<EObject>)company2.eGet(employees));
    company2Employees.add(john);
    assertEquals(company2, john.eContainer());
    assertTrue(((List<?>)company1.eGet(employees)).isEmpty());

    assertTrue(((XMLResourceImpl)xmlResource).getEObjectToIDMap().isEmpty());
    assertTrue(((XMLResourceImpl)xmlResource).getIDToEObjectMap().isEmpty());
    assertNull(xmlResource.getEObject("JOHN"));
  }
}