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


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

import org.eclipse.emf.common.command.AbstractCommand;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.mapping.Mapping;
import org.eclipse.emf.mapping.MappingPackage;
import org.eclipse.emf.mapping.MappingPlugin;
import org.eclipse.emf.mapping.domain.MappingDomain;


/**
 * The create mapping command creates a new mapping in a {@link MappingDomain} 
 * from a set of the domain's input and output objects.
 */
public class AddMappingCommand extends AbstractCommand 
{
  /**
   * This creates a command that adds the new mappings in the collection into the appropriate place in the mapping root's.
   */
  public static Command create(MappingDomain domain, Collection<?> collection)
  {
    return 
      domain.createCommand
        (AddMappingCommand.class, 
         new CommandParameter(domain.getMappingRoot(), null, collection));
  }

  /**
   * This creates a command that adds the new mappings in the collection into the appropriate place in the mapping root's.
   */
  public static Command create(MappingDomain domain, Mapping mapping)
  {
    return create(domain, Collections.singleton(mapping));
  }

  /**
   * This caches the label.
   */
  protected static final String LABEL = MappingPlugin.getPlugin().getString("_UI_AddMappingCommand_label");

  /**
   * This cachaes the description.
   */
  protected static final String DESCRIPTION = MappingPlugin.getPlugin().getString("_UI_AddMappingCommand_description");

  /**
   * This keeps track of the mapping domain in which the command operates.
   */
  protected MappingDomain domain;

  /**
   * This keeps track of the input and output objects that are to be mapped.
   */
  protected Collection<?> collection;

  /**
   * This keeps track of all the subcommand(s) use to implement this command.
   */
  Command subcommand;

  /**
   * This creates a command instance that adds the new mappings in the collection into the appropriate place in the mapping root's.
   */
  public AddMappingCommand(MappingDomain domain, Collection<?> collection)
  {
    super(LABEL, DESCRIPTION);

    this.domain = domain;
    this.collection = collection;
  }

  @Override
  protected boolean prepare() 
  {
    boolean result = domain != null;
    for (Object object : collection)
    {
      if (!(object instanceof Mapping))
      {
        result = false;
        break;
      }
    }

    return result;
  }

  public void execute()
  {
    // This will deal with all the subcommands to modifying the root mapping tree.
    //
    CompoundCommand subcommands = new CompoundCommand();

    // For each mapping being added...
    //
    for (Object object : collection)
    {
      Mapping mapping = (Mapping)object;

      // Find the appropriate parent mapping, which at the very least, should be the mapping root itself.
      //
      Mapping parentMapping = domain.getMappingRoot().getParentMapping(mapping.getMappedObjects());

      // Make sure the back pointers to this mapping from the mapped objects is set.
      //
      domain.getMappingRoot().register(mapping);

      // Create a command to do parentMapping.getNested().add(mapping).
      //
      //if (subcommands.appendAndExecute(new AddCommand(domain, parentMapping, parentMapping.ePackageMapping().getMapping_Nested(), mapping)))
      if (subcommands.appendAndExecute(new AddCommand(domain, parentMapping, MappingPackage.eINSTANCE.getMapping_Nested(), mapping)))
      {
        // Check all the siblings to see which if any should now be nested under this new mapping.
        // The are accumulated into a list so that they can be removed as a single command with a single notification.
        //
        Collection<Mapping> siblingsToReparent = new ArrayList<Mapping>();
        for (Mapping siblingMapping : parentMapping.getNested())
        {
          if (siblingMapping != mapping)
          {
            if (domain.getMappingRoot().getParentMapping(siblingMapping.getMappedObjects()) == mapping)
            {
              siblingsToReparent.add(siblingMapping);
            }
          }
        }

        // If there are siblings that need to be reparented.
        //
        if (!siblingsToReparent.isEmpty())
        {
          // Create commands to do parentMapping.getNested().removeAll(siblingsToReparent).
          //
          subcommands.appendAndExecute
            //(new RemoveCommand(domain, parentMapping, parentMapping.ePackageMapping().getMapping_Nested(), siblingsToReparent));
            (new RemoveCommand(domain, parentMapping, MappingPackage.eINSTANCE.getMapping_Nested(), siblingsToReparent));

          // Create commands to do mapping.getNested().addAll(siblingsToReparent).
          //
          subcommands.appendAndExecute
            //(new AddCommand(domain, mapping, mapping.ePackageMapping().getMapping_Nested(), siblingsToReparent));
            (new AddCommand(domain, mapping, MappingPackage.eINSTANCE.getMapping_Nested(), siblingsToReparent));
        }
      }
    }

    subcommand = subcommands.unwrap();
  }

  @Override
  public void undo() 
  {
    for (Object object : collection)
    {
      Mapping mapping = (Mapping)object;
      domain.getMappingRoot().deregister(mapping);
    }

    subcommand.undo();
  }

  public void redo()
  {
    for (Object object : collection)
    {
      Mapping mapping = (Mapping)object;
      domain.getMappingRoot().register(mapping);
    }

    subcommand.redo();
  }

  @Override
  public Collection<?> getResult() 
  {
    return collection;
  }

  @Override
  public Collection<?> getAffectedObjects() 
  {
    return collection;
  }

  @Override
  public void dispose()
  {
    if (subcommand != null)
    {
      subcommand.dispose();
    }
    super.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 + ")");
    result.append(" (collection: " + collection + ")");
    result.append(" (subcommand: " + subcommand + ")");

    return result.toString();
  }
}
