/**
 * Copyright (c) 2004-2008 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.edit.ui.action;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ISetSelectionTarget;

import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.ui.dialogs.DiagnosticDialog;
import org.eclipse.emf.common.ui.viewer.IViewerProvider;
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.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.emf.ecore.util.EObjectValidator;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.ui.EMFEditUIPlugin;


public class ValidateAction extends Action implements ISelectionChangedListener 
{
  private static final long serialVersionUID = 1L;

  protected ISelectionProvider selectionProvider;
  protected List<EObject> selectedObjects;
  protected EditingDomain domain;

  public ValidateAction()
  {
    super(EMFEditUIPlugin.INSTANCE.getString("_UI_Validate_menu_item"));
    setDescription(EMFEditUIPlugin.INSTANCE.getString("_UI_Validate_simple_description"));
  }

  @Override
  public void run()
  {
    final Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
    IRunnableWithProgress runnableWithProgress = new IRunnableWithProgress()
    {
      public void run(final IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException
      {
        try
        {
          final Diagnostic diagnostic = validate(progressMonitor);
          shell.getDisplay().asyncExec 
            (new Runnable()
             {
               public void run()
               {
                 if (progressMonitor.isCanceled())
                 {
                   handleDiagnostic(Diagnostic.CANCEL_INSTANCE);
                 }
                 else
                 {
                   handleDiagnostic(diagnostic);
                 }
               }
             });
        }
        finally
        {
          progressMonitor.done();
        }    
      }
    };
    
    try
    {
      // This runs the operation, and shows progress.
      // (It appears to be a bad thing to fork this onto another thread.)
      //
      new ProgressMonitorDialog(shell).run(true, true, runnableWithProgress);
    }
    catch (Exception exception)
    {
      EMFEditUIPlugin.INSTANCE.log(exception);
    }
  }
  
  /**
   * This simply execute the command.
   */
  protected Diagnostic validate(IProgressMonitor progressMonitor)
  {
    int selectionSize = selectedObjects.size();
    int count = selectionSize;
    for (EObject eObject : selectedObjects)
    {
      for (Iterator<?> i = eObject.eAllContents(); i.hasNext(); i.next())
      {
        ++count;
      }
    }

    progressMonitor.beginTask("", count);

    AdapterFactory adapterFactory = 
      domain instanceof AdapterFactoryEditingDomain ? ((AdapterFactoryEditingDomain)domain).getAdapterFactory() : null;
    Diagnostician diagnostician = createDiagnostician(adapterFactory, progressMonitor);
    
    BasicDiagnostic diagnostic;
    if (selectionSize == 1)
    {
      diagnostic = diagnostician.createDefaultDiagnostic(selectedObjects.get(0));
    }
    else
    {
      diagnostic =
        new BasicDiagnostic
          (EObjectValidator.DIAGNOSTIC_SOURCE,
           0,
           EMFEditUIPlugin.INSTANCE.getString("_UI_DiagnosisOfNObjects_message", new String[] { Integer.toString(selectionSize) }),
           selectedObjects.toArray() ); 
    }
    Map<Object, Object> context = diagnostician.createDefaultContext();
    for (EObject eObject : selectedObjects)
    {
      progressMonitor.setTaskName(EMFEditUIPlugin.INSTANCE.getString("_UI_Validating_message", new Object [] {diagnostician.getObjectLabel(eObject)}));
      diagnostician.validate(eObject, diagnostic, context);
    }
    return diagnostic;
  }

  protected Diagnostician createDiagnostician(final AdapterFactory adapterFactory, final IProgressMonitor progressMonitor)
  {
    return 
      new Diagnostician()
      {
        @Override
        public String getObjectLabel(EObject eObject)
        {
          if (adapterFactory != null && !eObject.eIsProxy())
          {
            IItemLabelProvider itemLabelProvider = (IItemLabelProvider)adapterFactory.adapt(eObject, IItemLabelProvider.class);
            if (itemLabelProvider != null)
            {
              return itemLabelProvider.getText(eObject);
            }
          }
  
          return super.getObjectLabel(eObject);
        }
  
        @Override
        public boolean validate(EClass eClass, EObject eObject, DiagnosticChain diagnostics, Map<Object, Object> context)
        {
          progressMonitor.worked(1);
          return super.validate(eClass, eObject, diagnostics, context);
        }
      };
  }

  protected void handleDiagnostic(Diagnostic diagnostic)
  {
    int severity = diagnostic.getSeverity();
    String title = null;
    String message = null;

    if (severity == Diagnostic.ERROR || severity == Diagnostic.WARNING)
    {
      title = EMFEditUIPlugin.INSTANCE.getString("_UI_ValidationProblems_title");
      message = EMFEditUIPlugin.INSTANCE.getString("_UI_ValidationProblems_message");
    }
    else
    {
      title = EMFEditUIPlugin.INSTANCE.getString("_UI_ValidationResults_title");
      message = EMFEditUIPlugin.INSTANCE.getString(severity == Diagnostic.OK ? "_UI_ValidationOK_message" : "_UI_ValidationResults_message");
    }

    int result = 0;
    if (diagnostic.getSeverity() == Diagnostic.OK)
    {
      MessageDialog.openInformation(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), title, message);
      result = Window.CANCEL;
    }
    else
    {
      result = DiagnosticDialog.open
        (PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), title, message, diagnostic);
    }

    if (result == Window.OK)
    {
      if (!diagnostic.getChildren().isEmpty())
      {
        List<?> data = (diagnostic.getChildren().get(0)).getData();
        if (!data.isEmpty() && data.get(0) instanceof EObject)
        {
          Object part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart();
          if (part instanceof ISetSelectionTarget)
          {
            ((ISetSelectionTarget)part).selectReveal(new StructuredSelection(data.get(0)));
          }
          else if (part instanceof IViewerProvider)
          {
            Viewer viewer = ((IViewerProvider)part).getViewer();
            if (viewer != null)
            {
              viewer.setSelection(new StructuredSelection(data.get(0)), true);
            }
          }
        }
      }
    }
  }
  
  public void selectionChanged(SelectionChangedEvent event) 
  {
    selectionProvider = event.getSelectionProvider();
    if (event.getSelection() instanceof IStructuredSelection)
    {
      setEnabled(updateSelection((IStructuredSelection)event.getSelection()));
    }
    else
    {
      setEnabled(false);
    }
  }

  public boolean updateSelection(IStructuredSelection selection)
  {
    selectedObjects = new ArrayList<EObject>();
    for (Iterator<?> objects = selection.iterator(); objects.hasNext(); )
    {
      Object object = AdapterFactoryEditingDomain.unwrap(objects.next());
      if (object instanceof EObject)
      {
        selectedObjects.add((EObject)object);
      }
      else if (object instanceof Resource)
      {
        selectedObjects.addAll(((Resource)object).getContents());
      }      
      else
      {
        return false;
      }
    }
    selectedObjects = EcoreUtil.filterDescendants(selectedObjects);
    return !selectedObjects.isEmpty();
  }

  /**
   * @since 2.1.0
   */
  public void setActiveWorkbenchPart(IWorkbenchPart workbenchPart)
  {
    if (workbenchPart instanceof IEditingDomainProvider)
    {
      domain = ((IEditingDomainProvider)workbenchPart).getEditingDomain();
    }
  }
}
