/*
 * Copyright 2003-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.nodeEditor;

import jetbrains.mps.kernel.model.SModelUtil;
import jetbrains.mps.openapi.editor.cells.CellTraversalUtil;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.smodel.ModelAccess;
import jetbrains.mps.smodel.SNodeLegacy;
import jetbrains.mps.smodel.SNodeUtil;
import jetbrains.mps.util.Computable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;

import java.util.HashSet;
import java.util.Set;

public class ChildrenCollectionFinder {
  @NotNull
  private final EditorCell myCurrent;
  @NotNull
  private final EditorCell myAnchor;
  private final boolean myForward;
  private final boolean myCheckFirst;

  public ChildrenCollectionFinder(@NotNull EditorCell current, @NotNull EditorCell anchor, boolean forward, boolean checkFirst) {
    myCurrent = current;
    myAnchor = anchor;
    myForward = forward;
    myCheckFirst = checkFirst;
  }

  public ChildrenCollectionFinder(@NotNull EditorCell current, boolean forward, boolean checkFirst) {
    this(current, current, forward, checkFirst);
  }

  public EditorCell find() {
    return ModelAccess.instance().runReadAction(new Computable<EditorCell>() {
      @Override
      public EditorCell compute() {
        if (myCheckFirst && isMultipleCollectionCell(myCurrent)) {
          return myCurrent;
        }
        SNode anchorNode = getLCA(myAnchor.getSNode(), myCurrent.getSNode());
        if (anchorNode == null) {
          return null;
        }
        // Note: traverser doesn't visit parent nodes of the current node,
        // if our anchor is from another subtree, search parents (up to LCA) first
        // TODO remove
        if (anchorNode != myCurrent.getSNode()) {
          EditorCell curr = myCurrent;
          while (curr != null && curr.getSNode() != anchorNode) {
            if (isMultipleCollectionCell(curr)) {
              return curr;
            }
            curr = curr.getParent();
          }
          if (curr != null && curr.getSNode() == anchorNode && isMultipleCollectionCell(curr)) {
            return curr;
          }
        }

        for (EditorCell current : CellTraversalUtil.iterateTree(null, myCurrent, myForward).skipStart()) {
          SNode currentNode = current.getSNode();

          if (!jetbrains.mps.util.SNodeOperations.isAncestor(anchorNode, currentNode)) {
            return null;
          }

          if (isMultipleCollectionCell(current)) {
            return current;
          }
        }
        return null;
      }
    });
  }

  private static boolean isMultipleCollectionCell(EditorCell current) {
    if (current.getRole() != null) {
      String role = current.getRole();
      SNode currentNode = current.getSNode();
      SNode linkDeclaration = new SNodeLegacy(currentNode).getLinkDeclaration(role);
      if (linkDeclaration != null &&
        !SNodeUtil.getLinkDeclaration_IsReference(linkDeclaration) &&
        SModelUtil.isMultipleLinkDeclaration(linkDeclaration)) {
        return true;
      }
    }
    return false;
  }

  private static SNode getLCA(SNode left, SNode right) {
    if (left == right || right == null || left == null) {
      return left == null ? right : left;
    }
    Set<SNode> parents = new HashSet<SNode>();
    while (left != null) {
      parents.add(left);
      left = left.getParent();
    }
    while (right != null) {
      if (parents.contains(right)) return right;
      right = right.getParent();
    }
    return null;
  }
}
