// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.json.pointer;

import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.stream.Collectors;

import static com.jetbrains.jsonSchema.JsonPointerUtil.*;

public class JsonPointerPosition {

  public JsonPointerPosition() {
    this.steps = ContainerUtil.newArrayList();
  }

  private JsonPointerPosition(List<Step> steps) {
    this.steps = steps;
  }

  public static JsonPointerPosition createSingleProperty(String property) {
    return new JsonPointerPosition(ContainerUtil.createMaybeSingletonList(Step.createPropertyStep(property)));
  }

  public static JsonPointerPosition parsePointer(@NotNull String pointer) {
    final List<String> chain = split(normalizeSlashes(normalizeId(pointer)));
    List<JsonPointerPosition.Step> steps = ContainerUtil.newArrayListWithCapacity(chain.size());
    for (String s: chain) {
      try {
        steps.add(JsonPointerPosition.Step.createArrayElementStep(Integer.parseInt(s)));
      }
      catch (NumberFormatException e) {
        steps.add(JsonPointerPosition.Step.createPropertyStep(unescapeJsonPointerPart(s)));
      }
    }
    return new JsonPointerPosition(steps);
  }

  List<Step> getSteps() {
    return steps;
  }

  private final List<Step> steps;

  public void addPrecedingStep(int value) {
    steps.add(0, Step.createArrayElementStep(value));
  }

  public void addFollowingStep(int value) {
    steps.add(Step.createArrayElementStep(value));
  }

  public void addPrecedingStep(String value) {
    steps.add(0, Step.createPropertyStep(value));
  }

  public void addFollowingStep(String value) {
    steps.add(Step.createPropertyStep(value));
  }

  public void replaceStep(int pos, int value) {
    steps.set(pos, Step.createArrayElementStep(value));
  }

  public void replaceStep(int pos, String value) {
    steps.set(pos, Step.createPropertyStep(value));
  }

  public boolean isEmpty() {
    return steps.isEmpty();
  }

  public boolean isArray(int pos) {
    return steps.size() >= pos && steps.get(pos).isFromArray();
  }

  public boolean isObject(int pos) {
    return steps.size() >= pos && steps.get(pos).isFromObject();
  }

  @Nullable
  public JsonPointerPosition skip(int count) {
    return steps.size() >= count ? new JsonPointerPosition(steps.subList(count, steps.size())) : null;
  }

  @Nullable
  public JsonPointerPosition trimTail(int count) {
    return steps.size() >= count ? new JsonPointerPosition(steps.subList(0, steps.size() - count)) : null;
  }

  @Nullable
  public String getLastName() {
    final Step last = ContainerUtil.getLastItem(steps);
    return last == null ? null : last.getName();
  }

  @Nullable
  public String getFirstName() {
    final Step last = ContainerUtil.getFirstItem(steps);
    return last == null ? null : last.getName();
  }

  public int getFirstIndex() {
    final Step last = ContainerUtil.getFirstItem(steps);
    return last == null ? -1 : last.getIdx();
  }

  public int size() {
    return steps.size();
  }

  public void updateFrom(JsonPointerPosition from) {
    steps.clear();
    steps.addAll(from.steps);
  }

  public String toJsonPointer() {
    return steps.stream().map(step -> escapeForJsonPointer(step.toString())).collect(Collectors.joining("/"));
  }

  @Override
  public String toString() {
    return steps.stream().map(Object::toString).collect(Collectors.joining("->", "steps: <", ">"));
  }

  static class Step {
    @Nullable private final String myName;
    private final int myIdx;

    private Step(@Nullable String name, int idx) {
      myName = name;
      myIdx = idx;
    }

    public static Step createPropertyStep(@NotNull final String name) {
      return new Step(name, -1);
    }

    public static Step createArrayElementStep(final int idx) {
      assert idx >= 0;
      return new Step(null, idx);
    }

    public boolean isFromObject() {
      return myName != null;
    }

    public boolean isFromArray() {
      return myName == null;
    }

    @Nullable
    public String getName() {
      return myName;
    }

    public int getIdx() {
      return myIdx;
    }

    @Override
    public String toString() {
      String format = "?%s";
      if (myName != null) format = "{%s}";
      if (myIdx >= 0) format = "[%s]";
      return String.format(format, myName != null ? myName : (myIdx >= 0 ? String.valueOf(myIdx) : "null"));
    }

  }
}

@@ -2214,35 +2214,36 @@
  return 
-steps.size() %3E= 
+checkPosInRange(
 pos
+)
  && step
@@ -2325,27 +2325,28 @@
 urn 
-steps.size() %3E= 
+checkPosInRange(
 pos
+)
  && 
@@ -2451,37 +2451,38 @@
  return 
-steps.size() %3E= 
+checkPosInRange(
 count
+)
  ? new J
@@ -2627,29 +2627,30 @@
 urn 
-steps.size() %3E= 
+checkPosInRange(
 count
+)
  ? n
@@ -3629,16 +3629,97 @@
 );%0A  %7D%0A%0A
+  private boolean checkPosInRange(int pos) %7B%0A    return steps.size() %3E pos;%0A  %7D%0A%0A
   static

fix range check