/**
 * Copyright (c) 2004-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 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.ecore.util;


//import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.plugin.EcorePlugin;

/**
 * A validity checker for basic EObject constraints.
 */
public class Diagnostician implements EValidator.SubstitutionLabelProvider, EValidator
{
  public static final Diagnostician INSTANCE = new Diagnostician();

  protected EValidator.Registry eValidatorRegistry;

  public Diagnostician(EValidator.Registry eValidatorRegistry)
  {
    this.eValidatorRegistry = eValidatorRegistry;
  }

  public Diagnostician()
  {
    this(EValidator.Registry.INSTANCE);
  }

  public String getObjectLabel(EObject eObject)
  {
    return EcoreUtil.getIdentification(eObject);
  }

  public String getFeatureLabel(EStructuralFeature eStructuralFeature)
  {
    return eStructuralFeature.getName();
  }

  public String getValueLabel(EDataType eDataType, Object value)
  {
    return EcoreUtil.convertToString(eDataType, value);
  }

  /**
   * @since 2.4
   */
  public Map<Object, Object> createDefaultContext()
  {
    Map<Object, Object> context = new HashMap<Object, Object>();
    context.put(EValidator.SubstitutionLabelProvider.class, this);
    context.put(EValidator.class, this);
    return context;
  }

  /**
   * @since 2.4
   */
  public BasicDiagnostic createDefaultDiagnostic(EObject eObject)
  {
    return
      new BasicDiagnostic
        (EObjectValidator.DIAGNOSTIC_SOURCE,
         0,
         EcorePlugin.INSTANCE.getString("_UI_DiagnosticRoot_diagnostic", new Object[] { getObjectLabel(eObject) }),
         new Object [] { eObject });    
  }

  /**
   * @since 2.4
   */
  public BasicDiagnostic createDefaultDiagnostic(EDataType eDataType, Object value)
  {
    return
      new BasicDiagnostic
        (EObjectValidator.DIAGNOSTIC_SOURCE,
         0,
         EcorePlugin.INSTANCE.getString("_UI_DiagnosticRoot_diagnostic", new Object [] { getValueLabel(eDataType, value) }),
         new Object [] { value, eDataType });    
  }

  public Diagnostic validate(EObject eObject)
  {
    BasicDiagnostic diagnostics = createDefaultDiagnostic(eObject);
    validate(eObject, diagnostics, createDefaultContext());
    return diagnostics;
  }

  /**
   * @since 2.4
   */
  public Diagnostic validate(EObject eObject, Map<?, ?> contextEntries)
  {
    BasicDiagnostic diagnostics = createDefaultDiagnostic(eObject);
    Map<Object, Object> context = createDefaultContext();
    context.putAll(contextEntries);
    validate(eObject, diagnostics, context);
    return diagnostics;    
  }

  /**
   * Validates the object in the given context, optionally producing diagnostics.
   * @param eObject the object to validate.
   * @param diagnostics a place to accumulate diagnostics; if it's <code>null</code>, no diagnostics should be produced.
   * @return whether the object is valid.
   */
  public boolean validate(EObject eObject, DiagnosticChain diagnostics)
  {
    return validate(eObject, diagnostics, createDefaultContext());
  }

  public boolean validate(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    return validate(eObject.eClass(), eObject, diagnostics, context); 
  }

  public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    Object eValidator;
    EClass eType = eClass;
    while ((eValidator = eValidatorRegistry.get(eType.eContainer())) == null)
    {
      List<EClass> eSuperTypes = eType.getESuperTypes();
      if (eSuperTypes.isEmpty())
      {
        eValidator = eValidatorRegistry.get(null);
        break;
      }
      else
      {
        eType = eSuperTypes.get(0);
      }
    }
    boolean circular = context.get(EObjectValidator.ROOT_OBJECT) == eObject;
    boolean result = ((EValidator)eValidator).validate(eClass, eObject, diagnostics, context);
    if ((result || diagnostics != null) && !circular)
    {
      result &= doValidateContents(eObject, diagnostics, context);
    }
    return result;
  }

  protected boolean doValidateContents(EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    List<EObject> eContents = eObject.eContents();
    if (!eContents.isEmpty())
    {
      Iterator<EObject> i = eContents.iterator(); 
      EObject child = i.next();
      boolean result = validate(child, diagnostics, context);
      while (i.hasNext() && (result || diagnostics != null))
      {
        child = i.next();
        result &= validate(child, diagnostics, context);
      }
      return result;
    }
    else
    {
      return true;
    }
  }

  public Diagnostic validate(EDataType eDataType, Object value)
  {
    BasicDiagnostic diagnostics = createDefaultDiagnostic(eDataType, value);
    validate(eDataType, value, diagnostics, createDefaultContext());
    return diagnostics;
  }

  public boolean validate(EDataType eDataType, Object value, DiagnosticChain diagnostics, Map<Object, Object> context)
  {
    Object eValidator = eValidatorRegistry.get(eDataType.eContainer());
    if (eValidator == null)
    {
      eValidator = eValidatorRegistry.get(null);
    }

    return ((EValidator)eValidator).validate(eDataType, value, diagnostics, context);
  }
}
