package jetbrains.mps.refactoring.participant.plugin;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import java.util.List;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.module.SRepository;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.baseLanguage.closures.runtime.Wrappers;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.model.SModel;
import java.util.Objects;
import java.util.Map;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.project.Project;
import jetbrains.mps.refactoring.participant.RefactoringUI;
import jetbrains.mps.internal.collections.runtime.IMapping;
import org.jetbrains.mps.openapi.model.SNodeReference;
import java.util.ArrayList;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.refactoring.participant.RefactoringParticipant;
import jetbrains.mps.refactoring.participant.RefactoringProcessor;
import jetbrains.mps.refactoring.participant.RefactoringSession;
import jetbrains.mps.smodel.structure.ExtensionPoint;
import jetbrains.mps.refactoring.participant.MoveNodeRefactoringParticipant;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.refactoring.participant.NodeCopyTracker;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import jetbrains.mps.ide.platform.refactoring.NodeLocation;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import org.jetbrains.mps.openapi.language.SProperty;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;

@GeneratedClass(node = "r:cc08a4fa-e4f1-443c-b8f2-4a41972141bb(jetbrains.mps.refactoring.participant.plugin)/4661652150171662291", model = "r:cc08a4fa-e4f1-443c-b8f2-4a41972141bb(jetbrains.mps.refactoring.participant.plugin)")
public class MoveNodesUtil {


  public static boolean areSiblings(final List<SNode> nodesToMove, SRepository repository) {
    if (ListSequence.fromList(nodesToMove).isEmpty()) {
      return false;
    }
    final Wrappers._boolean result = new Wrappers._boolean();
    repository.getModelAccess().runReadAction(() -> {
      SNode firstNode = ListSequence.fromList(nodesToMove).first();
      final SContainmentLink containmentLink = firstNode.getContainmentLink();
      final SNode parent = firstNode.getParent();
      final SModel model = firstNode.getModel();
      result.value = ListSequence.fromList(nodesToMove).all((it) -> Objects.equals(it.getContainmentLink(), containmentLink) && it.getParent() == parent && it.getModel() == model);
    });
    return result.value;
  }

  public static class ListIndex<T> {
    private Map<T, Integer> myIndices = MapSequence.fromMap(new HashMap<T, Integer>());
    public ListIndex(List<T> patternList) {
      for (int i = 0; i < ListSequence.fromList(patternList).count(); i++) {
        MapSequence.fromMap(myIndices).put(ListSequence.fromList(patternList).getElement(i), i);
      }
    }
    public int getIndex(T object) {
      return MapSequence.fromMap(myIndices).get(object);
    }
    public <S> S getCorrespondent(List<S> list, T anchor) {
      return ListSequence.fromList(list).getElement(getIndex(anchor));
    }
  }

  public static void moveTo(final MPSProject project, final String refactoringName, final Map<NodeProcessor, List<SNode>> processorToMoveRoots) {
    moveTo(project, refactoringName, processorToMoveRoots, new DefaultRefactoringUI(project, refactoringName));
  }

  public static void moveTo(final Project project, final String refactoringName, final Map<NodeProcessor, List<SNode>> processorToMoveRoots, RefactoringUI refactoringUI) {

    project.getRepository().getModelAccess().runReadAction(() -> {
      for (IMapping<NodeProcessor, List<SNode>> mapping : MapSequence.fromMap(processorToMoveRoots)) {
        if (!(mapping.key().isValid(mapping.value()))) {
          throw new IllegalArgumentException();
        }
      }
    });

    final Map<SNodeReference, List<SNodeReference>> moveRootsToDescendants = MapSequence.fromMap(new HashMap<SNodeReference, List<SNodeReference>>());
    final List<SNode> allNodes = ListSequence.fromList(new ArrayList<SNode>());
    project.getRepository().getModelAccess().runReadAction(() -> {
      for (IMapping<NodeProcessor, List<SNode>> mapping : MapSequence.fromMap(processorToMoveRoots)) {
        NodeProcessor processor = mapping.key();
        for (SNode moveRoot : ListSequence.fromList(mapping.value())) {
          List<SNode> nodesToSearch = processor.getNodesToSearch(moveRoot);
          MapSequence.fromMap(moveRootsToDescendants).put(SNodeOperations.getPointer(moveRoot), ListSequence.fromList(nodesToSearch).select((it) -> it.getReference()).toList());
          ListSequence.fromList(allNodes).addSequence(ListSequence.fromList(nodesToSearch));
        }
      }
    });
    final ListIndex<SNodeReference> nodeChangesCorrespondence = new ListIndex<SNodeReference>(ListSequence.fromList(allNodes).select((it) -> SNodeOperations.getPointer(it)).toList());

    final Map<SNode, RefactoringParticipant.KeepOldNodes> removeOldRoots = MapSequence.fromMap(new HashMap<SNode, RefactoringParticipant.KeepOldNodes>());
    RefactoringProcessor.RefactoringBody<SNode, SNode> usagesUpdate = new RefactoringProcessor.RefactoringBody<SNode, SNode>() {
      private RefactoringSession myRefactoringSession = null;
      @Override
      public String getRefactoringName() {
        return refactoringName;
      }
      @Override
      public Iterable<? extends RefactoringParticipant<?, ?, SNode, SNode>> getAllAvailableParticipants() {
        return new ExtensionPoint<MoveNodeRefactoringParticipant<?, ?>>("jetbrains.mps.refactoring.participant.MoveNodeParticipantEP").getObjects();
      }
      @Override
      public List<SNode> findInitialStates() {
        return allNodes;
      }
      @Override
      public void prepareRefactoring() {
        // do nothing
      }
      @Override
      public void doRefactor(final Iterable<RefactoringParticipant.ParticipantApplied<?, ?, SNode, SNode, SNode, SNode>> participantStates, RefactoringSession refactoringSession) {
        myRefactoringSession = refactoringSession;

        for (IMapping<NodeProcessor, List<SNode>> mapping : MapSequence.fromMap(processorToMoveRoots)) {
          List<SNode> moveRoots = mapping.value();
          NodeProcessor processor = mapping.key();

          for (SNode moveRoot : ListSequence.fromList(moveRoots)) {
            MapSequence.fromMap(removeOldRoots).put(moveRoot, RefactoringParticipant.KeepOldNodes.max(ListSequence.fromList(MapSequence.fromMap(moveRootsToDescendants).get(SNodeOperations.getPointer(moveRoot))).translate((final SNodeReference descendant) -> Sequence.fromIterable(participantStates).select((participantState) -> {
              List<? extends RefactoringParticipant.Change<?, ?>> changes = nodeChangesCorrespondence.getCorrespondent(participantState.getChanges(), descendant);
              return RefactoringParticipant.KeepOldNodes.max(ListSequence.fromList(changes).select((change) -> ((MoveNodeRefactoringParticipant.MoveNodeChange<?, ?>) ((RefactoringParticipant.Change) change)).needsToPreserveOldNode()));
            }))));
          }

          processor.process(moveRoots, removeOldRoots, refactoringSession);
        }
      }
      @Override
      public SNode getFinalStateFor(SNode initialState) {
        return MapSequence.fromMap(NodeCopyTracker.get(myRefactoringSession).getCopyMap()).get(initialState);
      }
      @Override
      public void doCleanup() {
        for (NodeProcessor processor : SetSequence.fromSet(MapSequence.fromMap(processorToMoveRoots).keySet())) {
          processor.removeAfterRefactoring(removeOldRoots, myRefactoringSession);
        }
      }
    };
    RefactoringProcessor.performRefactoringInProject(project, refactoringUI, usagesUpdate);
  }

  public static abstract class NodeProcessor {
    public abstract boolean isValid(List<SNode> nodesToMove);
    public abstract boolean isValid(SNode nodeToMove);
    public abstract List<SNode> getNodesToSearch(SNode nodeToMove);
    public abstract void process(List<SNode> nodesToMove, Map<SNode, RefactoringParticipant.KeepOldNodes> ifKeepOldNodes, RefactoringSession refactoringSession);
    public void removeAfterRefactoring(Map<SNode, RefactoringParticipant.KeepOldNodes> ifKeepOldNodes, RefactoringSession refactoringSession) {
      for (SNode oldNode : SetSequence.fromSet(MapSequence.fromMap(ifKeepOldNodes).keySet())) {
        if (MapSequence.fromMap(ifKeepOldNodes).get(oldNode) != RefactoringParticipant.KeepOldNodes.KEEP) {
          SNodeOperations.deleteNode(oldNode);
        }
      }
    }

  }

  public static class NodeCreatingProcessor extends NodeProcessor {
    protected NodeLocation myNodeLocation;
    protected Project myProject;
    public NodeCreatingProcessor(NodeLocation location, Project project) {
      myNodeLocation = location;
      myProject = project;
    }
    public boolean isValid(List<SNode> nodesToMove) {
      return ListSequence.fromList(nodesToMove).all((it) -> isValid(it));
    }
    public boolean isValid(SNode nodeToMove) {
      return myNodeLocation.canInsert(myProject.getRepository(), nodeToMove);
    }
    public List<SNode> getNodesToSearch(SNode nodeToMove) {
      return SNodeOperations.getNodeDescendants(nodeToMove, null, true, new SAbstractConcept[]{});
    }
    public void process(List<SNode> nodeRoots, Map<SNode, RefactoringParticipant.KeepOldNodes> ifKeepOldNodes, RefactoringSession refactoringSession) {
      NodeCopyTracker copyMap = NodeCopyTracker.get(refactoringSession);
      copyMap.copyAndTrack(nodeRoots);
      for (SNode oldNode : ListSequence.fromList(nodeRoots)) {
        SNode newNode = MapSequence.fromMap(copyMap.getCopyMap()).get(oldNode);
        if (myNodeLocation.isRoot()) {
          SPropertyOperations.assign(newNode, PROPS.virtualPackage$EkXl, SPropertyOperations.getString(SNodeOperations.getContainingRoot(oldNode), PROPS.virtualPackage$EkXl));
        }
        if (MapSequence.fromMap(ifKeepOldNodes).get(oldNode) == RefactoringParticipant.KeepOldNodes.REMOVE) {
          SNodeOperations.deleteNode(oldNode);
        }
        myNodeLocation.insertNode(myProject.getRepository(), newNode);
      }
    }
  }

  public static class ExistingTargetProcessor extends NodeProcessor {
    private SNodeReference myTarget;
    private Project myProject;
    public ExistingTargetProcessor(SNodeReference target, Project project) {
      myTarget = target;
      myProject = project;
    }
    public boolean isValid(List<SNode> nodesToMove) {
      return ListSequence.fromList(nodesToMove).all((it) -> isValid(it));
    }
    public boolean isValid(SNode nodeToMove) {
      return myTarget.resolve(myProject.getRepository()) != null;
    }
    public List<SNode> getNodesToSearch(SNode nodeToMove) {
      return ListSequence.fromListAndArray(new ArrayList<SNode>(), nodeToMove);
    }
    public void process(List<SNode> nodesToMove, Map<SNode, RefactoringParticipant.KeepOldNodes> ifKeepOldNodes, RefactoringSession refactoringSession) {
      if (ListSequence.fromList(nodesToMove).count() != 1) {
        throw new IllegalArgumentException();
      }
      NodeCopyTracker copyMap = NodeCopyTracker.get(refactoringSession);
      copyMap.putInCopyMap(MapSequence.fromMapAndEntryArray(new HashMap<SNode, SNode>(), Map.entry(ListSequence.fromList(nodesToMove).first(), myTarget.resolve(myProject.getRepository()))));
      for (SNode oldNode : SetSequence.fromSet(MapSequence.fromMap(ifKeepOldNodes).keySet())) {
        if (MapSequence.fromMap(ifKeepOldNodes).get(oldNode) == RefactoringParticipant.KeepOldNodes.REMOVE) {
          SNodeOperations.deleteNode(oldNode);
        }
      }
    }
  }


  private static final class PROPS {
    /*package*/ static final SProperty virtualPackage$EkXl = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x10802efe25aL, 0x115eca8579fL, "virtualPackage");
  }
}
