/**
 * Copyright (c) 2002-2012 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.codegen.ecore.genmodel.presentation;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.viewers.ISelection;
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.Viewer;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.BaseSelectionListenerAction;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.codegen.ecore.CodeGenEcorePlugin;
import org.eclipse.emf.codegen.ecore.generator.Generator;
import org.eclipse.emf.codegen.ecore.genmodel.GenAnnotation;
import org.eclipse.emf.codegen.ecore.genmodel.GenBase;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.codegen.ecore.genmodel.generator.GenBaseGeneratorAdapter;
import org.eclipse.emf.codegen.ecore.genmodel.provider.GenModelEditPlugin;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandWrapper;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.common.ui.action.ViewerFilterAction;
import org.eclipse.emf.common.ui.viewer.IViewerProvider;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.edit.ui.action.CollapseAllAction;
import org.eclipse.emf.edit.ui.action.CommandActionHandler;
import org.eclipse.emf.edit.ui.action.EditingDomainActionBarContributor;
import org.eclipse.emf.edit.ui.action.ExpandAllAction;
import org.eclipse.emf.edit.ui.action.FindAction;
import org.eclipse.emf.edit.ui.action.RevertAction;
import org.eclipse.emf.edit.ui.provider.DiagnosticDecorator;
import org.eclipse.emf.edit.ui.util.EditUIUtil;


/**
 * This is the action bar contributor for the GenModel model editor.
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 * @generated NOT
 */
public class GenModelActionBarContributor
  extends EditingDomainActionBarContributor
  implements ISelectionChangedListener
{
  /**
   * This keeps track of the active editor.
   */
  protected IEditorPart activeEditorPart;

  /**
   * This gets the selection from the active editor.
   */
  protected ISelection getActiveEditorSelection()
  {
    return activeEditorPart == null ? null :
      ((GenModelEditor)activeEditorPart).getSelection();
  }

  /**
   * This keeps track of the current selection provider.
   */
  protected ISelectionProvider selectionProvider;

  /**
   * This action opens the Properties view.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  protected IAction showPropertiesViewAction =
    new Action(GenModelEditPlugin.INSTANCE.getString("_UI_ShowPropertiesView_menu_item"))
    {
      @Override
      public void run()
      {
        try
        {
          getPage().showView("org.eclipse.ui.views.PropertySheet");
        }
        catch (PartInitException exception)
        {
          GenModelEditPlugin.INSTANCE.log(exception);
        }
      }
    };

  /**
   * This action refreshes the viewer of the current editor if the editor
   * implements {@link org.eclipse.emf.common.ui.viewer.IViewerProvider}.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  protected IAction refreshViewerAction =
    new Action(GenModelEditPlugin.INSTANCE.getString("_UI_RefreshViewer_menu_item"))
    {
      @Override
      public boolean isEnabled()
      {
        return activeEditorPart instanceof IViewerProvider;
      }

      @Override
      public void run()
      {
        if (activeEditorPart instanceof IViewerProvider)
        {
          Viewer viewer = ((IViewerProvider)activeEditorPart).getViewer();
          if (viewer != null)
          {
            viewer.refresh();
          }
        }
      }
    };

  /**
   * This is the menu manager for the "Generate" menu.
   */
  protected IMenuManager generateMenuManager;

  protected IAction generateModelAction = new GenerateAction
    (GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE,
     CodeGenEcorePlugin.INSTANCE.getString("_UI_ModelProject_name"),
     GenModelEditPlugin.INSTANCE.getString("_UI_GenerateModel_menu_item"));

  protected IAction generateEditAction = new GenerateAction
    (GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE,
     CodeGenEcorePlugin.INSTANCE.getString("_UI_EditProject_name"),
     GenModelEditPlugin.INSTANCE.getString("_UI_GenerateEdit_menu_item"));

  protected IAction generateEditorAction = new GenerateAction
    (GenBaseGeneratorAdapter.EDITOR_PROJECT_TYPE,
     CodeGenEcorePlugin.INSTANCE.getString("_UI_EditorProject_name"),
     GenModelEditPlugin.INSTANCE.getString("_UI_GenerateEditor_menu_item"));

  protected IAction generateTestsAction = new GenerateAction
    (GenBaseGeneratorAdapter.TESTS_PROJECT_TYPE ,
     CodeGenEcorePlugin.INSTANCE.getString("_UI_TestsProject_name"),
     GenModelEditPlugin.INSTANCE.getString("_UI_GenerateTests_menu_item"));

  protected IAction generateAllAction = new GenerateAction
  (new ProjectType[]
   {
     new ProjectType(GenBaseGeneratorAdapter.MODEL_PROJECT_TYPE, CodeGenEcorePlugin.INSTANCE.getString("_UI_ModelProject_name")),
     new ProjectType(GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE, CodeGenEcorePlugin.INSTANCE.getString("_UI_EditProject_name")),
     new ProjectType(GenBaseGeneratorAdapter.EDITOR_PROJECT_TYPE, CodeGenEcorePlugin.INSTANCE.getString("_UI_EditorProject_name")),
     new ProjectType(GenBaseGeneratorAdapter.TESTS_PROJECT_TYPE, CodeGenEcorePlugin.INSTANCE.getString("_UI_TestsProject_name"))
   },
   GenModelEditPlugin.INSTANCE.getString("_UI_GenerateAll_menu_item"));

  /**
   * This implements the "Generate..." actions.
   */
  protected class GenerateAction extends Action
  {
    ProjectType[] projectTypes;
    Generator generator;

    public GenerateAction(Object projectType, String projectTypeName, String text)
    {
      super(text);
      this.projectTypes = new ProjectType[] { new ProjectType(projectType, projectTypeName) };
    }

    public GenerateAction(ProjectType[] projectTypes, String text)
    {
      super(text);
      this.projectTypes = projectTypes;
    }

    @Override
    public boolean isEnabled()
    {
      if (activeEditorPart instanceof GenModelEditor)
      {
        generator = ((GenModelEditor)activeEditorPart).getGenerator();
      }

      if (generator == null)
      {
        return false;
      }

      ISelection s = getActiveEditorSelection();
      if (!(s instanceof IStructuredSelection))
      {
        return false;
      }

      IStructuredSelection ss = (IStructuredSelection)s;
      if (ss.size() == 0)
      {
        return false;
      }

      for (Object object : ss.toList())
      {
        boolean canGenerateObject = false;

        for (int i = 0; i < projectTypes.length; i++)
        {
          if (generator.canGenerate(object, projectTypes[i].getType()))
          {
            canGenerateObject = true;
          }
        }

        if (!canGenerateObject)
        {
          return false;
        }
      }
      return true;
    }

    @Override
    public void run()
    {
      GeneratorUIUtil.GeneratorOperation operation =
        new GeneratorUIUtil.GeneratorOperation(activeEditorPart.getSite().getShell());
      operation.setRootDiagnosticMessage(getText());

      GenModel genModel = null;
      Collection<?> selection = ((IStructuredSelection)getActiveEditorSelection()).toList();
      for (Object object : selection)
      {
        for (int i = 0; i < projectTypes.length; i++)
        {
          if (object instanceof GenBase)
          {
            genModel = ((GenBase)object).getGenModel();
          }
          operation.addGeneratorAndArguments(generator, object, projectTypes[i].getType(), projectTypes[i].getName());
        }
      }

      Set<IProject> projects = new HashSet<IProject>();
      Set<IWorkingSet> workingSets = new HashSet<IWorkingSet>();
      if (genModel != null)
      {
        String modelDirectory = genModel.getModelDirectory();
        if (modelDirectory != null && !"".equals(modelDirectory))
        {
          IProject project = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(modelDirectory)).getProject();
          if (project != null)
          {
            IWorkbench workbench = PlatformUI.getWorkbench();
            for (IWorkingSet workingSet : workbench.getWorkingSetManager().getAllWorkingSets())
            {
              IAdaptable[] elements = workingSet.getElements();
              for (IAdaptable element : elements)
              {
                if (project.equals(element.getAdapter(IProject.class)))
                {
                  workingSets.add(workingSet);
                  continue;
                }
              }
            }
            if (!workingSets.isEmpty())
            {
              for (int i = 0; i < projectTypes.length; i++)
              {
                Object projectType = projectTypes[i].getType();
                if (GenBaseGeneratorAdapter.EDIT_PROJECT_TYPE.equals(projectType) && genModel.hasEditSupport())
                {
                  IProject editProject = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(genModel.getEditDirectory())).getProject();
                  if (!editProject.exists())
                  {
                    projects.add(editProject);
                  }
                }
                if (GenBaseGeneratorAdapter.EDITOR_PROJECT_TYPE.equals(projectType) && genModel.hasEditorSupport())
                {
                  IProject editorProject = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(genModel.getEditorDirectory())).getProject();
                  if (!editorProject.exists())
                  {
                    projects.add(editorProject);
                  }
                }
                if (GenBaseGeneratorAdapter.TESTS_PROJECT_TYPE.equals(projectType) && genModel.hasTestSupport())
                {
                  IProject testsProject = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(genModel.getTestsDirectory())).getProject();
                  if (!testsProject.exists())
                  {
                    projects.add(testsProject);
                  }
                }
              }
            }
          }
        }
      }

      // This runs the options, and shows progress.
      // (It appears to be a bad thing to fork this onto another thread.)
      //
      try
      {
        new ProgressMonitorDialog(activeEditorPart.getSite().getShell()).run(true, true, operation);

        if (!projects.isEmpty())
        {
          for (IWorkingSet workingSet : workingSets)
          {
            List<IAdaptable> elements = new ArrayList<IAdaptable>(Arrays.asList(workingSet.getElements()));
            elements.addAll(projects);
            workingSet.setElements(workingSet.adaptElements(elements.toArray(new IAdaptable[elements.size()])));
          }
        }
      }
      catch (Exception exception)
      {
        // Something went wrong that shouldn't.
        //
        GenModelEditPlugin.INSTANCE.log(exception);
      }
    }
  }

  protected static class ProjectType
  {
    protected Object type;
    protected String name;

    public ProjectType(Object type, String name)
    {
      this.type = type;
      this.name = name;
    }

    public Object getType()
    {
      return type;
    }

    public String getName()
    {
      return name;
    }
  }

  protected ViewerFilterAction showGenAnnotationsAction = new ViewerFilterAction(GenModelEditPlugin.INSTANCE.getString("_UI_ShowGenAnnotation_menu_item"), IAction.AS_CHECK_BOX)
  {
    @Override
    public boolean select(Viewer viewer, Object parentElement, Object element)
    {
      return !(element instanceof GenAnnotation) || isChecked();
    }
  };

  protected abstract class CreateAction extends CommandActionHandler
  {
    protected String label;

    public CreateAction(String text, String label)
    {
      super(null, text);
      this.label = label;
    }

    public void dispose()
    {
      setEditingDomain(null);
    }

    @Override
    public Command createCommand(Collection<?> selection)
    {
      if (activeEditorPart instanceof IEditingDomainProvider)
      {
        setEditingDomain(((IEditingDomainProvider)activeEditorPart).getEditingDomain());
      }

      if (getEditingDomain() != null && selection.size() == 1)
      {
        Object selectedObject = selection.iterator().next();
        if (selectedObject instanceof GenBase)
        {
          Command command = doCreateCommand((GenBase)selectedObject);
          if (command != null)
          {
            command = new CommandWrapper(label, null, command);
          }
          return command;
        }
      }
      return UnexecutableCommand.INSTANCE;
    }

    protected abstract Command doCreateCommand(GenBase selectedObject);
  }

  protected CreateAction annotateAction = new CreateAction(
    GenModelEditPlugin.INSTANCE.getString("_UI_Annotate_menu_item"),
    GenModelEditPlugin.INSTANCE.getString("_UI_Annotate_text"))
  {
    @Override
    protected Command doCreateCommand(GenBase selectedObject)
    {
      return AddCommand.create(getEditingDomain(), selectedObject, GenModelPackage.Literals.GEN_BASE__GEN_ANNOTATIONS, selectedObject.getGenModel().createGenAnnotation());
    }
  };

  protected CreateAction addDetailAction = new CreateAction(
    GenModelEditPlugin.INSTANCE.getString("_UI_AddDetail_menu_item"),
    GenModelEditPlugin.INSTANCE.getString("_UI_AddDetail_text"))
  {
    @Override
    protected Command doCreateCommand(GenBase selectedObject)
    {
      return AddCommand.create(getEditingDomain(), selectedObject, GenModelPackage.Literals.GEN_ANNOTATION__DETAILS, EcoreUtil.create(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY));
    }
  };

  protected static abstract class OpenEObjectEditorAction extends BaseSelectionListenerAction
  {
    protected EObject eObject;

    public OpenEObjectEditorAction(String text)
    {
      super(text);
    }

    public void dispose()
    {
      eObject = null;
      clearCache();
    }

    @Override
    public void run()
    {
      if (eObject != null)
      {
        try
        {
          EditUIUtil.openEditor(eObject);
        }
        catch (PartInitException e)
        {
          e.printStackTrace();
        }
      }
    }

    @Override
    protected boolean updateSelection(IStructuredSelection selection)
    {
      if (selection.size() == 1)
      {
        Object element = selection.getFirstElement();
        eObject = getEObject(element);
        return eObject != null;
      }
      return false;
    }

    protected abstract EObject getEObject(Object element);
  }

  protected OpenEObjectEditorAction openEcoreAction = new OpenEObjectEditorAction(GenModelEditPlugin.INSTANCE.getString("_UI_OpenEcore_menu_item"))
  {
    @Override
    protected EObject getEObject(Object element)
    {
      return element instanceof GenBase ? ((GenBase)element).getEcoreModelElement() : null;
    }
  };

  protected OpenEObjectEditorAction openGenModelAction = new OpenEObjectEditorAction(GenModelEditPlugin.INSTANCE.getString("_UI_OpenGenModel_menu_item"))
  {
    @Override
    protected EObject getEObject(Object element)
    {
      if (activeEditorPart instanceof IEditingDomainProvider && element instanceof EObject)
      {
        EObject eObject = (EObject)element;
        EditingDomain editingDomain = ((IEditingDomainProvider)activeEditorPart).getEditingDomain();
        if (editingDomain.getResourceSet().getResources().indexOf(eObject.eResource()) != 0)
        {
          return eObject;
        }
      }
      return null;
    }
  };


  /**
   * This creates an instance of the contributor.
   */
  public GenModelActionBarContributor()
  {
    super(ADDITIONS_LAST_STYLE);
    liveValidationAction = new DiagnosticDecorator.LiveValidator.LiveValidationAction(GenModelEditPlugin.getPlugin().getDialogSettings());
    showGenAnnotationsAction.setChecked
     (Boolean.parseBoolean(GenModelEditPlugin.getPlugin().getDialogSettings().get("showGenAnnotationsAction")));
    findAction = FindAction.create();
    revertAction = new RevertAction();
    expandAllAction = new ExpandAllAction();
    collapseAllAction = new CollapseAllAction();
  }

  @Override
  public void dispose()
  {
    GenModelEditPlugin.getPlugin().getDialogSettings().put(
      "showGenAnnotationsAction", Boolean.toString(showGenAnnotationsAction.isChecked()));

    showGenAnnotationsAction.dispose();
    annotateAction.dispose();
    addDetailAction.dispose();
    openEcoreAction.dispose();
    openGenModelAction.dispose();

    super.dispose();
  }

  /**
   * This adds menu contributions for the generate actions.
   */
  @Override
  public void contributeToMenu(IMenuManager menuManager)
  {
    super.contributeToMenu(menuManager);

    generateMenuManager =
      new MenuManager(GenModelEditPlugin.INSTANCE.getString("_UI_Generate_menu"), "org.eclipse.emf.codegen.ecore.genmodelMenuID");
    menuManager.insertAfter("additions", generateMenuManager);
    generateMenuManager.add(generateModelAction);
    generateMenuManager.add(generateEditAction);
    generateMenuManager.add(generateEditorAction);
    generateMenuManager.add(generateTestsAction);
    generateMenuManager.add(generateAllAction);

    generateMenuManager.add(new Separator("annotation-actions"));
    generateMenuManager.add(showGenAnnotationsAction);

    generateMenuManager.add(new Separator("validation-actions"));
    generateMenuManager.add(liveValidationAction);

    generateMenuManager.add(new Separator("global-actions"));
  }

  /**
   * This adds Separators for editor additions to the tool bar.
   */
  @Override
  public void contributeToToolBar(IToolBarManager toolBarManager)
  {
    super.contributeToToolBar(toolBarManager);
    toolBarManager.add(new Separator("genmodel-settings"));
    toolBarManager.add(new Separator("genmodel-additions"));
  }

  /**
   * When the active editor changes, this remembers the change,
   */
  @Override
  public void setActiveEditor(IEditorPart part)
  {
    super.setActiveEditor(part);

    if (part instanceof GenModelEditor)
    {
      showGenAnnotationsAction.addViewer(((GenModelEditor)part).getViewer());
      showGenAnnotationsAction.setEnabled(true);
    }
    else
    {
      showGenAnnotationsAction.setEnabled(false);
    }

    activeEditorPart = part;

    // Switch to the new selection provider.
    //
    if (selectionProvider != null)
    {
      selectionProvider.removeSelectionChangedListener(this);
    }
    if (part == null)
    {
      selectionProvider = null;
    }
    else
    {
      selectionProvider = part.getSite().getSelectionProvider();
      selectionProvider.addSelectionChangedListener(this);

      // Fake a selection changed event to update the menus.
      //
      if (selectionProvider.getSelection() != null)
      {
        selectionChanged(new SelectionChangedEvent(selectionProvider, selectionProvider.getSelection()));
      }
    }
  }

  /**
   * This implements {@link ISelectionChangedListener}, refreshing the
   * "Generate..." action contribution managers in the pull-down menu.
   */
  public void selectionChanged(SelectionChangedEvent event)
  {
    IContributionItem[] items = generateMenuManager.getItems();
    for (int i = 0, len = items.length; i < len; i++) items[i].update();

    annotateAction.selectionChanged(event);
    addDetailAction.selectionChanged(event);
    openEcoreAction.selectionChanged(event);
    openGenModelAction.selectionChanged(event);
  }

  /**
   * This populates the pop-up menu before it appears.
   */
  @Override
  public void menuAboutToShow(IMenuManager menuManager)
  {
    generateAllAction.setEnabled(generateAllAction.isEnabled());
    generateTestsAction.setEnabled(generateTestsAction.isEnabled());
    generateEditorAction.setEnabled(generateEditorAction.isEnabled());
    generateEditAction.setEnabled(generateEditAction.isEnabled());
    generateModelAction.setEnabled(generateModelAction.isEnabled());
    refreshViewerAction.setEnabled(refreshViewerAction.isEnabled());

    super.menuAboutToShow(menuManager);

    menuManager.insertBefore("edit", new Separator("generate-actions"));
    menuManager.insertAfter("generate-actions", generateAllAction);
    menuManager.insertAfter("generate-actions", generateTestsAction);
    menuManager.insertAfter("generate-actions", generateEditorAction);
    menuManager.insertAfter("generate-actions", generateEditAction);
    menuManager.insertAfter("generate-actions", generateModelAction);

    menuManager.insertBefore("edit", new Separator("open-actions"));
    menuManager.insertAfter("open-actions", openGenModelAction);
    menuManager.insertAfter("open-actions", openEcoreAction);

    if (showGenAnnotationsAction.isChecked())
    {
      menuManager.insertBefore("edit", new Separator("annotation-actions"));
      if (addDetailAction.isEnabled()) menuManager.insertAfter("annotation-actions", addDetailAction);
      if (annotateAction.isEnabled()) menuManager.insertAfter("annotation-actions", annotateAction);
    }
  }

  /**
   * This inserts global actions before the "additions-end" separator.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  protected void addGlobalActions(IMenuManager menuManager)
  {
    menuManager.insertAfter("additions-end", new Separator("ui-actions"));
    menuManager.insertAfter("ui-actions", showPropertiesViewAction);

    refreshViewerAction.setEnabled(refreshViewerAction.isEnabled());
    menuManager.insertAfter("ui-actions", refreshViewerAction);

    super.addGlobalActions(menuManager);
  }

}
