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


import java.util.Collection;
import java.util.List;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PlatformUI;

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandWrapper;
import org.eclipse.emf.common.ui.dialogs.ResourceDialog;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
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.ui.EMFEditUIPlugin;


/**
 * A control action moves a contained object into a new resource or restores it to its containers' resource.
 * It is implemented by creating a {@link AddCommand} or {@link RemoveCommand}.
 * @since 2.2.0
 */
public class ControlAction extends CommandActionHandler
{
  protected IStructuredSelection selection = null;
  protected EObject eObject = null;

  public ControlAction(EditingDomain domain)
  {
    super(domain, EMFEditUIPlugin.INSTANCE.getString("_UI_Control_menu_item"));
  }
 
  public ControlAction()
  {
    this(null);
  }

  // We can create the RemoveCommand for an uncontrol, but we must defer creating an AddCommand
  // to control until run(), when the user specifies a target resource.
  //
  @Override
  public boolean updateSelection(IStructuredSelection selection)
  {
    this.selection = selection;
    if (selection.size() != 1) return false;

    Object object = AdapterFactoryEditingDomain.unwrap(selection.getFirstElement());
    boolean result = domain.isControllable(object);
    eObject = result ? (EObject)object : null;

    if (!AdapterFactoryEditingDomain.isControlled(object))
    {
      setText(EMFEditUIPlugin.INSTANCE.getString("_UI_Control_menu_item"));
      setDescription("_UI_Control_menu_item_description");
      command = null;
    }
    else
    {
      setText(EMFEditUIPlugin.INSTANCE.getString("_UI_Uncontrol_menu_item"));
      setDescription("_UI_Uncontrol_menu_item_description");
      if (result)
      {
        command = new RemoveCommand(domain, eObject.eResource().getContents(), eObject); 
        command = new SelfAffectingCommand(EMFEditUIPlugin.INSTANCE.getString("_UI_UncontrolCommand_label"), command);
        result = command.canExecute();
      }
    }
    return result;
  }

  protected class SelfAffectingCommand extends CommandWrapper
  {
    SelfAffectingCommand(String label, Command command)
    {
      super(label, command);
    }

    @Override
    public Collection<?> getResult()
    {
      return selection.toList();
    }

    @Override
    public Collection<?> getAffectedObjects()
    {
      return selection.toList();
    }
  }

  // For the control case, we need a dialog to ask for the URI and obtain the resource, then we create the command.
  //
  @Override
  public void run()
  {
    if (command == null)
    {
      if (eObject == null) return;

      Resource resource = getResource();
      if (resource == null) return;

      command = new AddCommand(domain, resource.getContents(), eObject);
      command = new SelfAffectingCommand(EMFEditUIPlugin.INSTANCE.getString("_UI_ControlCommand_label"), command);
    }

    // Ensure that all proxies are resolved so that references into the controlled object will be saved to reference the new resource.
    //
    EcoreUtil.resolveAll(domain.getResourceSet());

    super.run();
  }
  
  protected Resource getResource()
  {
    ControlResourceDialog dialog = new ControlResourceDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), domain, eObject.eResource());
    dialog.open();
    return dialog.getResource();
  }

  public void setActiveWorkbenchPart(IWorkbenchPart workbenchPart)
  {
    if (workbenchPart instanceof IEditingDomainProvider)
    {
      domain = ((IEditingDomainProvider)workbenchPart).getEditingDomain();
    }
  }

  /**
   * A save-type {@link ResourceDialog resource dialog} that attempts to create the specified resource and
   * load it, if it already exists.
   */
  protected static class ControlResourceDialog extends ResourceDialog
  {
    protected EditingDomain domain;
    protected Resource resource;
    protected Resource currentResource;

    public ControlResourceDialog(Shell parent, EditingDomain domain, Resource currentResource)
    {
      super(parent, EMFEditUIPlugin.INSTANCE.getString("_UI_ControlDialog_title"), SWT.SAVE, getContextURI(domain));
      this.domain = domain;
      this.currentResource = currentResource;
    }

    private static URI getContextURI(EditingDomain domain)
    {
      if (domain != null)
      {
        List<Resource> resources = domain.getResourceSet().getResources();
        if (!resources.isEmpty())
        {
          return resources.get(0).getURI();
        }
      }
      return null;
    }

    /**
     * Creates and, if it already exists, loads the specified resource. This implementation verifies that a
     * resource can be opened for that URI, that the resource is not the object's current container, and that
     * it is not read-only in the editing domain. If there is an existing resource with that URI, it prompts
     * before overriding or adding to it.
     */
    @Override
    protected boolean processResources()
    {
      List<URI> uris = getURIs();
      if (uris.isEmpty()) return false;

      URI uri = uris.get(0);
      ResourceSet resourceSet = domain.getResourceSet();
      Resource resource = resourceSet.getResource(uri, false);
      boolean resourceInSet = resource != null;

      if (resource == currentResource)
      {
        MessageDialog.openError(getShell(), EMFEditUIPlugin.INSTANCE.getString("_UI_InvalidURI_label"), EMFEditUIPlugin.INSTANCE.getString("_WARN_AlreadyInResource"));
        return false;        
      }
      if (resource != null && domain.isReadOnly(resource))
      {
        MessageDialog.openError(getShell(), EMFEditUIPlugin.INSTANCE.getString("_UI_InvalidURI_label"), EMFEditUIPlugin.INSTANCE.getString("_WARN_ReadOnlyResource"));
        return false;
      }

      boolean resourceExists = resourceSet.getURIConverter().exists(uri, null);

      boolean resourceBad = false;
      if (!resourceInSet)
      {
        resource = resourceSet.createResource(uri);
        if (resource == null)
        {
          MessageDialog.openError(getShell(), EMFEditUIPlugin.INSTANCE.getString("_UI_InvalidURI_label"), EMFEditUIPlugin.INSTANCE.getString("_WARN_CannotCreateResource"));
          return false;
        }

        if (resourceExists)
        {
          try
          {
            resource = resourceSet.getResource(uri, true);
          }
          catch (RuntimeException exception)
          {
            EMFEditUIPlugin.INSTANCE.log(exception);
            resourceBad = resource.getContents().isEmpty();            
          }
        }
      }

      boolean result = true;
      if (resourceBad)
      {
        result = MessageDialog.openQuestion(getShell(), EMFEditUIPlugin.INSTANCE.getString("_UI_ExistingResource_label"), EMFEditUIPlugin.INSTANCE.getString("_WARN_ReplaceResource"));
      }
      else if (resourceExists)
      {
        result = MessageDialog.openQuestion(getShell(), EMFEditUIPlugin.INSTANCE.getString("_UI_ExistingResource_label"), EMFEditUIPlugin.INSTANCE.getString("_WARN_AddToResource"));
      }

      if (!result && !resourceInSet && resource != null)
      {
        resource.unload();
        resourceSet.getResources().remove(resource);
      }
      else
      {
        this.resource = resource;
      }
      return result;
    }

    public Resource getResource()
    {
     return resource; 
    }
  }
}
