/**
 * Copyright (c) 2002-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.edit.command;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandWrapper;
import org.eclipse.emf.common.command.StrictCompoundCommand;
import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.domain.EditingDomain;


/**
 * This works exactly like an {@link AddCommand} but the things to be added are copied from the {@link EditingDomain} clipboard.
 * If the copied clipboard instance is of the same type as the original clipboard instance, 
 * the clipboard instance is replaced by the copied instance and the original instance is used for the add.
 */
public class PasteFromClipboardCommand extends AbstractOverrideableCommand
{
  /**
   * This creates a command to add copies from the clipboard to the specified feature of the owner.
   */
  public static Command create(EditingDomain domain, Object owner, Object feature)
  {
    return create(domain, owner, feature, CommandParameter.NO_INDEX);
  }

  /**
   * This creates a command to add copies from the clipboard to the specified feature of the owner and at the given index.
   */
  public static Command create(EditingDomain domain, Object owner, Object feature, int index)
  {
    if (domain == null)
    {
      return new PasteFromClipboardCommand(domain, owner, feature, index, true);
    }
    else
    {
      Command command = 
        domain.createCommand(PasteFromClipboardCommand.class, new CommandParameter(owner, feature, Collections.emptyList(), index));
      return command;
    }
  }
    
  /**
   * This caches the label.
   */
  protected static final String LABEL = EMFEditPlugin.INSTANCE.getString("_UI_PasteFromClipboardCommand_label");

  /**
   * This caches the description.
   */
  protected static final String DESCRIPTION = EMFEditPlugin.INSTANCE.getString("_UI_PasteFromClipboardCommand_description");

  /**
   * This is the command that does the actual pasting.
   */
  protected StrictCompoundCommand command;

  /**
   * This is object where the clipboard copy is pasted.
   */
  protected Object owner;

  /**
   * This is feature of the owner where the clipboard copy is pasted.
   */
  protected Object feature;

  /**
   * This is index in the feature of the owner where the clipboard copy is pasted.
   */
  protected int index;

  /**
   * This controls whether or not to optimize the canExecute (prepare)
   */
  protected boolean optimize;

  /**
   * This constructs an instance from the domain, which provides access the clipboard collection 
   * via {@link EditingDomain#getCommandStack}.
   */
  public PasteFromClipboardCommand(EditingDomain domain, Object owner, Object feature, int index)
  {
    this(domain, owner, feature, index, true);
  }

  public PasteFromClipboardCommand(EditingDomain domain, Object owner, Object feature, int index, boolean optimize)
  {
    super(domain, LABEL, DESCRIPTION);

    this.owner = owner;
    this.feature = feature;
    this.index = index;
    this.optimize = optimize;
  }

  public Object getOwner()
  {
    return owner;
  }

  public Object getFeature()
  {
    return feature;
  }

  public int getIndex()
  {
    return index;
  }

  @Override
  protected boolean prepare()
  {
    // Create a strict compound command to do a copy and then add the result
    //
    command = new StrictCompoundCommand();

    // Create a command to copy the clipboard.
    //
    final Command copyCommand = CopyCommand.create(domain, domain.getClipboard());
    command.append(copyCommand);

    // Create a proxy that will create an add command.
    //
    command.append
      (new CommandWrapper()
       {
         protected Collection<Object> original;
         protected Collection<Object> copy;

         @Override
         protected Command createCommand()
         {
           original = domain.getClipboard();
           copy = new ArrayList<Object>(copyCommand.getResult());

           // Use the original to do the add, but only if it's of the same type as the copy.
           // This ensures that if there is conversion being done as part of the copy,
           // as would be the case for a cross domain copy in the mapping framework,
           // that we do actually use the converted instance.
           //
           if (original.size() == copy.size())
           {
             for (Iterator<Object> i = original.iterator(), j = copy.iterator(); i.hasNext(); )
             {
               Object originalObject = i.next();
               Object copyObject = j.next();
               if (originalObject.getClass() != copyObject.getClass())
               {
                 original = null;
                 break;
               }
             }
           }
           
           Command addCommand = AddCommand.create(domain, owner, feature, original == null ? copy : original, index);
           return addCommand;
         }

         @Override
         public void execute()
         {
           if (original != null)
           {
             domain.setClipboard(copy);
           }
           super.execute();
         }

         @Override
         public void undo()
         {
           super.undo();
           if (original != null)
           {
             domain.setClipboard(original);
           }
         }

         @Override
         public void redo()
         {
           if (original != null)
           {
             domain.setClipboard(copy);
           }
           super.redo();
         }
       });

    boolean result;
    if (optimize)
    {
      // This will determine canExecute as efficiently as possible.
      //
      result = optimizedCanExecute();
    }
    else
    {
      // This will actually execute the copy command in order to check if the add can execute.
      //
      result = command.canExecute();
    }

    return result;
  }

  protected boolean optimizedCanExecute()
  {
    // We'll assume that the copy command can execute and that adding a copy of the clipboard
    // is the same test as adding the clipboard contents itself.
    //
    Command addCommand = AddCommand.create(domain, owner, feature, domain.getClipboard());
    boolean result = addCommand.canExecute();
    addCommand.dispose();
    return result;
  }

  @Override
  public void doExecute()
  {
    // We need to check canExecute() here in case prepare() went down the "optimize" path.
    //
    if (command.canExecute())
    {
      command.execute();
    }  
    else
    {
      // Thread.dumpStack();
    }
  }

  @Override
  public void doUndo()
  {
    command.undo();
  }

  @Override
  public void doRedo()
  {
    command.redo();
  }

  @Override
  public Collection<?> doGetResult()
  {
    return command.getResult();
  }

  @Override
  public Collection<?> doGetAffectedObjects()
  {
    return command.getAffectedObjects();
  }

  @Override
  public void doDispose()
  {
    if (command != null) command.dispose();
  }

  /**
   * This gives an abbreviated name using this object's own class' name, without package qualification,
   * followed by a space separated list of <tt>field:value</tt> pairs.
   */
  @Override
  public String toString()
  {
    StringBuffer result = new StringBuffer(super.toString());
    result.append(" (domain: " + domain + ")");

    return result.toString();
  }
}
