package jetbrains.mps.kotlin.behavior;

/*Generated by MPS */

import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.IAttributeDescriptor;
import jetbrains.mps.baseLanguage.behavior.IIncompleteParen__BehaviorDescriptor;
import jetbrains.mps.smodel.action.SNodeFactoryOperations;
import java.util.List;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.ArrayList;
import java.util.Objects;
import jetbrains.mps.baseLanguage.behavior.IBinaryLike__BehaviorDescriptor;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SInterfaceConcept;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SProperty;

public class ParenthesisUtil {
  public ParenthesisUtil() {
  }

  private static SNode findWrappingParens(SNode original) {
    SNode current = original;
    while (SNodeOperations.isInstanceOf(SNodeOperations.getParent(current), CONCEPTS.Expression$mB)) {
      current = SNodeOperations.cast(SNodeOperations.getParent(current), CONCEPTS.Expression$mB);
      if (SNodeOperations.isInstanceOf(current, CONCEPTS.ParenthesizedExpression$Ws)) {
        return current;
      }
    }
    return null;
  }

  private static void setOrIncreaseParen(SNode node, boolean right) {
    SNode paren = (right ? new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc).get(node) : new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7).get(node));
    if (paren != null) {
      IIncompleteParen__BehaviorDescriptor.increaseCount_idVufYxgmE1y.invoke(paren);
    } else {
      if (right) {
        SNodeFactoryOperations.setNewAttribute(node, new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc), CONCEPTS.IncompleteRightParen$Sc);
      } else {
        SNodeFactoryOperations.setNewAttribute(node, new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7), CONCEPTS.IncompleteLeftParen$Z7);
      }
    }
  }

  private static void setOrMergeParen(SNode myNode, boolean right, SNode parens) {
    SNode nodesParens = (right ? new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc).get(myNode) : new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7).get(myNode));
    if (nodesParens == null) {
      if (right) {
        nodesParens = SNodeFactoryOperations.setNewAttribute(myNode, new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc), CONCEPTS.IncompleteRightParen$Sc);
      } else {
        nodesParens = SNodeFactoryOperations.setNewAttribute(myNode, new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7), CONCEPTS.IncompleteLeftParen$Z7);
      }
    }
    while (!((boolean) IIncompleteParen__BehaviorDescriptor.isSingleParen_idVufYxgmHsD.invoke(parens))) {
      IIncompleteParen__BehaviorDescriptor.decreaseCount_idVufYxgmFtR.invoke(parens);
      IIncompleteParen__BehaviorDescriptor.increaseCount_idVufYxgmE1y.invoke(nodesParens);
    }
  }


  /**
   * Returns an orderred list of nodes with incomplete left/right paren
   * The nodes are orderred by their occurence on the expression in left-to-right order
   * The current node is added at the front of the list,
   * so that it is always the most distant node among the returned candidates
   */
  private static List<SNode> descendInto(SNode expr, final boolean completingByRightParen) {
    List<SNode> path = ListSequence.fromListAndArray(new ArrayList<SNode>(), null, expr);
    List<SNode> result = ListSequence.fromList(new ArrayList<SNode>());

    SNode current = expr;
    while (ListSequence.fromList(path).isNotEmpty()) {
      if (SNodeOperations.isInstanceOf(current, CONCEPTS.IBinaryLike$eU)) {
        if (Objects.equals(ListSequence.fromList(path).last(), current)) {
          SNode left = IBinaryLike__BehaviorDescriptor.getSyntacticallyLeftSideExpression_id1wHCnsn590c.invoke(SNodeOperations.cast(current, CONCEPTS.IBinaryLike$eU));
          if (left != null) {
            ListSequence.fromList(path).addElement(left);
            current = left;
          } else {
            current = ListSequence.fromList(path).removeLastElement();
          }
        } else {
          ListSequence.fromList(result).addElement(current);
          SNode right = IBinaryLike__BehaviorDescriptor.getSyntacticallyRightSideExpression_id1wHCnsn590i.invoke(SNodeOperations.cast(current, CONCEPTS.IBinaryLike$eU));
          if (right != null) {
            ListSequence.fromList(path).addElement(right);
            current = right;
          } else {
            current = ListSequence.fromList(path).removeLastElement();
          }
        }
      } else {
        ListSequence.fromList(result).addElement(current);
        if (Objects.equals(ListSequence.fromList(path).last(), current)) {
          ListSequence.fromList(path).removeLastElement();
        }
        current = ListSequence.fromList(path).removeLastElement();
      }
    }
    if (!(completingByRightParen)) {
      result = ListSequence.fromList(result).reversedList();
    }

    result = ListSequence.fromList(result).where((it) -> completingByRightParen && (new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7).get(it) != null) || !(completingByRightParen) && (new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc).get(it) != null)).toList();

    return result;
  }



  private static void rebalanceTernaryOpAfterParenthing(SNode node, SNode rightTurn, SNode leftTurn, SNode parens, SNode rightAccumulator, SNode leftAccumulator) {
    SNode bottomMostTernary = findBottomMostTernary(node);
    if (rightTurn != null) {
      SNodeOperations.replaceWithAnother(node, SLinkOperations.getTarget(bottomMostTernary, LINKS.ifFalse$Wbma));
      if (leftTurn != null) {
        IBinaryLike__BehaviorDescriptor.setSyntacticallyLeftSideExpression_id1wHCnsn58ZK.invoke(rightTurn, SLinkOperations.getTarget(node, LINKS.condition$nwNI));
        IBinaryLike__BehaviorDescriptor.setSyntacticallyRightSideExpression_id1wHCnsn58ZY.invoke(leftTurn, parens);
      } else {
        IBinaryLike__BehaviorDescriptor.setSyntacticallyLeftSideExpression_id1wHCnsn58ZK.invoke(rightTurn, parens);
      }
    } else {
      if (leftTurn != null) {
        SNodeOperations.replaceWithAnother(node, SLinkOperations.getTarget(node, LINKS.condition$nwNI));
        IBinaryLike__BehaviorDescriptor.setSyntacticallyRightSideExpression_id1wHCnsn58ZY.invoke(leftTurn, parens);
      } else {
        SNodeOperations.replaceWithAnother(node, parens);
      }
    }
    SLinkOperations.setTarget(bottomMostTernary, LINKS.ifFalse$Wbma, rightAccumulator);
    SLinkOperations.setTarget(node, LINKS.condition$nwNI, leftAccumulator);
  }

  /**
   * Ternary expressions can be nested in one anothers ifFalse child. Find the right-most one.
   * 
   * @param bottomMostTernary The so-far most-bottom ternary operator expression
   * @return The found right-most ternary operator expression.
   */
  private static SNode findBottomMostTernary(SNode bottomMostTernary) {
    while (SNodeOperations.isInstanceOf(SLinkOperations.getTarget(bottomMostTernary, LINKS.ifFalse$Wbma), CONCEPTS.TernaryOperatorExpression$aq)) {
      bottomMostTernary = SNodeOperations.cast(SLinkOperations.getTarget(bottomMostTernary, LINKS.ifFalse$Wbma), CONCEPTS.TernaryOperatorExpression$aq);
    }
    return bottomMostTernary;
  }

  /**
   * Accumulate the nodes that need to be added into the parentheses
   */
  private static SNode buildAccumulator(SNode firstCommonAncestor, SNode turn, boolean left) {
    SNode accumulator;
    if (turn != null && !(Objects.equals(turn, firstCommonAncestor))) {
      // Accumulate nodes on the path up from the left/right paren
      accumulator = (left ? IBinaryLike__BehaviorDescriptor.getSyntacticallyRightSideExpression_id1wHCnsn590i.invoke(turn) : IBinaryLike__BehaviorDescriptor.getSyntacticallyLeftSideExpression_id1wHCnsn590c.invoke(turn));
      SNodeOperations.deleteNode(accumulator);
      SNode current = SNodeOperations.cast(SNodeOperations.getParent(turn), CONCEPTS.IBinaryLike$eU);
      SNode previous = turn;
      while (!(Objects.equals(current, firstCommonAncestor))) {
        SNode sideExpression = (left ? IBinaryLike__BehaviorDescriptor.getSyntacticallyLeftSideExpression_id1wHCnsn590c.invoke(current) : IBinaryLike__BehaviorDescriptor.getSyntacticallyRightSideExpression_id1wHCnsn590i.invoke(current));
        if (sideExpression != null && Objects.equals(sideExpression, previous)) {
          SNodeOperations.replaceWithAnother(current, previous);
          if (left) {
            IBinaryLike__BehaviorDescriptor.setSyntacticallyLeftSideExpression_id1wHCnsn58ZK.invoke(current, accumulator);
          } else {
            IBinaryLike__BehaviorDescriptor.setSyntacticallyRightSideExpression_id1wHCnsn58ZY.invoke(current, accumulator);
          }
          accumulator = SNodeOperations.cast(current, CONCEPTS.Expression$mB);
        } else {
          previous = current;
        }
        current = SNodeOperations.cast(SNodeOperations.getParent(previous), CONCEPTS.IBinaryLike$eU);
      }
    } else {
      // Nothing to accumulate
      SNode firstCommonAncestorChild = (left ? IBinaryLike__BehaviorDescriptor.getSyntacticallyLeftSideExpression_id1wHCnsn590c.invoke(firstCommonAncestor) : IBinaryLike__BehaviorDescriptor.getSyntacticallyRightSideExpression_id1wHCnsn590i.invoke(firstCommonAncestor));
      if (firstCommonAncestorChild != null) {
        accumulator = firstCommonAncestorChild;
        SNodeOperations.deleteNode(accumulator);
      } else {
        accumulator = null;
      }
    }
    return accumulator;
  }

  /**
   * Remove the found matching paren annotation from its node
   */
  private static void clearIncompleteParens(SNode otherExpression, boolean completingByRightParen, SNode parens) {
    int count = 0;
    if (completingByRightParen) {
      count = SPropertyOperations.getInteger(new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7).get(otherExpression), PROPS.count$FDpi);
      new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7).set(otherExpression, null);
      new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteLeftParen$Z7).set(parens, null);
    } else {
      count = SPropertyOperations.getInteger(new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc).get(otherExpression), PROPS.count$FDpi);
      new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc).set(otherExpression, null);
      new IAttributeDescriptor.NodeAttribute(CONCEPTS.IncompleteRightParen$Sc).set(parens, null);
    }

    // One paren has been matched, so we should decrease the count
    count -= 1;
    if (count < 1) {
      return;
    }
    for (int i = 0; i < count; i++) {
      setOrIncreaseParen(parens, !(completingByRightParen));
    }
  }

  /**
   * Climb up the ancestor expressions and return the first binary operation, in which we come from the right sub-tree.
   * 
   * @param stopNode Never climb beyond this node
   */
  private static SNode findLeftTurn(SNode leaf, SNode stopNode) {
    return findTurn(leaf, stopNode, true);
  }

  /**
   * Climb up the ancestor expressions and return the first binary operation, in which we come from the left sub-tree.
   * 
   * @param stopNode Never climb beyond this node
   */
  private static SNode findRightTurn(SNode leaf, SNode stopNode) {
    return findTurn(leaf, stopNode, false);
  }

  private static SNode findTurn(SNode leaf, SNode stopNode, boolean leftTurn) {
    SNode currentNode = SNodeOperations.getParent(leaf);
    SNode previous = leaf;
    while (!(Objects.equals(previous, stopNode)) && SNodeOperations.isInstanceOf(currentNode, CONCEPTS.IBinaryLike$eU)) {
      SNode leftSideExpression = IBinaryLike__BehaviorDescriptor.getSyntacticallyLeftSideExpression_id1wHCnsn590c.invoke(SNodeOperations.cast(currentNode, CONCEPTS.IBinaryLike$eU));
      SNode rightSideExpression = IBinaryLike__BehaviorDescriptor.getSyntacticallyRightSideExpression_id1wHCnsn590i.invoke(SNodeOperations.cast(currentNode, CONCEPTS.IBinaryLike$eU));
      if (leftTurn && Objects.equals(rightSideExpression, previous)) {
        return SNodeOperations.cast(currentNode, CONCEPTS.IBinaryLike$eU);
      }
      if (!(leftTurn) && Objects.equals(leftSideExpression, previous)) {
        return SNodeOperations.cast(currentNode, CONCEPTS.IBinaryLike$eU);
      }

      previous = currentNode;
      currentNode = SNodeOperations.getParent(currentNode);
    }
    return null;
  }


  /**
   * Find all the ancestor expressions of leaf that could be wrapped in parens.
   * Stops when the parent current expression could no longer be parenthesised.
   */
  private static List<SNode> parentPath(SNode leaf, boolean rightParen) {
    List<SNode> path = new ArrayList<SNode>();
    ListSequence.fromList(path).addElement(leaf);
    List<SNode> leafAncestors = SNodeOperations.getNodeAncestors(leaf, null, true);

    for (SNode currentNode = SNodeOperations.getParent(leaf); SNodeOperations.isInstanceOf(currentNode, CONCEPTS.IBinaryLike$eU) && (boolean) IBinaryLike__BehaviorDescriptor.canPropagateUnmatchedParenUp_id1wHCnsn58ZA.invoke(SNodeOperations.cast(currentNode, CONCEPTS.IBinaryLike$eU), leaf, ((boolean) rightParen)); currentNode = SNodeOperations.getParent(currentNode)) {
      ListSequence.fromList(path).addElement(SNodeOperations.cast(currentNode, CONCEPTS.Expression$mB));
    }
    return path;
  }




  public static SNode getBinOp(SNode expr, boolean toRight) {
    SNode parent = SNodeOperations.getParent(expr);
    if (!(SNodeOperations.isInstanceOf(parent, CONCEPTS.BinaryOperation$W1))) {
      return null;
    }
    SNode parentOp = SNodeOperations.cast(parent, CONCEPTS.BinaryOperation$W1);
    boolean right = toRight && SLinkOperations.getTarget(parentOp, LINKS.leftExpression$sEj) == expr;
    boolean left = !(toRight) && SLinkOperations.getTarget(parentOp, LINKS.rightExpression$nvX) == expr;
    if (right || left) {
      return parentOp;
    }
    return getBinOp(parentOp, toRight);
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept Expression$mB = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c37f506fL, "jetbrains.mps.baseLanguage.structure.Expression");
    /*package*/ static final SConcept ParenthesizedExpression$Ws = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfb4ed32b7fL, "jetbrains.mps.baseLanguage.structure.ParenthesizedExpression");
    /*package*/ static final SConcept IncompleteRightParen$Sc = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x2052c4520af308e1L, "jetbrains.mps.baseLanguage.structure.IncompleteRightParen");
    /*package*/ static final SConcept IncompleteLeftParen$Z7 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x64a1ef64cd9b42ceL, "jetbrains.mps.baseLanguage.structure.IncompleteLeftParen");
    /*package*/ static final SInterfaceConcept IBinaryLike$eU = MetaAdapterFactory.getInterfaceConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x182da1771714863eL, "jetbrains.mps.baseLanguage.structure.IBinaryLike");
    /*package*/ static final SConcept TernaryOperatorExpression$aq = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10ef01239c9L, "jetbrains.mps.baseLanguage.structure.TernaryOperatorExpression");
    /*package*/ static final SConcept BinaryOperation$W1 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfbdeb6fecfL, "jetbrains.mps.baseLanguage.structure.BinaryOperation");
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink ifFalse$Wbma = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10ef01239c9L, 0x10ef012cedcL, "ifFalse");
    /*package*/ static final SContainmentLink condition$nwNI = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10ef01239c9L, 0x10ef012826fL, "condition");
    /*package*/ static final SContainmentLink leftExpression$sEj = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfbdeb6fecfL, 0xfbdeb7a11cL, "leftExpression");
    /*package*/ static final SContainmentLink rightExpression$nvX = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfbdeb6fecfL, 0xfbdeb7a11bL, "rightExpression");
  }

  private static final class PROPS {
    /*package*/ static final SProperty count$FDpi = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xede3fe8510255edL, 0xede3fe8510255eeL, "count");
  }
}
