package org.jetbrains.plugins.textmate.regex;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import org.jcodings.specific.UTF8Encoding;
import org.jetbrains.annotations.NotNull;
import org.joni.Matcher;
import org.joni.Option;
import org.joni.Regex;
import org.joni.exception.JOniException;

import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.ExecutionException;

public class RegexFacade {
  private static final Regex FAILED_REGEX = new Regex("^$", UTF8Encoding.INSTANCE);
  private static final Logger LOGGER = Logger.getInstance(RegexFacade.class);

  @NotNull
  private final Regex myRegex;

  private Object lastId = null;
  private int lastOffset = Integer.MAX_VALUE;
  private MatchData lastMatch = MatchData.NOT_MATCHED;

  private RegexFacade(@NotNull String regexString) {
    byte[] bytes = regexString.getBytes(StandardCharsets.UTF_8);
    Regex regex;
    try {
      regex = new Regex(bytes, 0, bytes.length, Option.CAPTURE_GROUP, UTF8Encoding.INSTANCE);
    }
    catch (JOniException e) {
      LOGGER.info("Failed to parse textmate regex", e);
      regex = FAILED_REGEX;
    }
    myRegex = regex;
  }

  public MatchData match(StringWithId string) {
    return match(string, 0);
  }

  public MatchData match(@NotNull StringWithId string, int byteOffset) {
    if (lastId == string.id && lastOffset <= byteOffset) {
      if (!lastMatch.matched() || lastMatch.byteOffset().getStartOffset() >= byteOffset) {
        return lastMatch;
      }
    }
    ProgressManager.checkCanceled();
    lastId = string.id;
    lastOffset = byteOffset;
    final Matcher matcher = myRegex.matcher(string.bytes);
    int matchIndex = matcher.search(byteOffset, string.bytes.length, Option.CAPTURE_GROUP);
    lastMatch = matchIndex > -1 ? MatchData.fromRegion(matcher.getEagerRegion()) : MatchData.NOT_MATCHED;
    return lastMatch;
  }

  public Searcher searcher(byte[] stringBytes) {
    return new Searcher(stringBytes, myRegex.matcher(stringBytes, 0, stringBytes.length));
  }

  private static final LoadingCache<String, RegexFacade> REGEX_CACHE = CacheBuilder.newBuilder()
    .maximumSize(1024)
    .softValues()
    .build(CacheLoader.from(regexString -> new RegexFacade(Objects.requireNonNull(regexString))));

  @NotNull
  public static RegexFacade regex(@NotNull String regexString) {
    try {
      return REGEX_CACHE.get(regexString);
    }
    catch (ExecutionException e) {
      return new RegexFacade(regexString);
    }
  }
}


@@ -271,24 +271,66 @@
 essManager;%0A
+import com.intellij.openapi.util.Trinity;%0A
 import org.j
@@ -360,16 +360,16 @@
 coding;%0A
-
 import o
@@ -880,129 +880,96 @@
 ate 
-Object lastId = null;%0A  private int lastOffset = Integer.MAX_VALUE;%0A  private MatchData lastMatch = MatchData.NOT_MATCHED
+final ThreadLocal%3CTrinity%3CObject, Integer, MatchData%3E%3E matchResult = new ThreadLocal%3C%3E()
 ;%0A%0A 
@@ -1509,24 +1509,333 @@
 teOffset) %7B%0A
+    Trinity%3CObject, Integer, MatchData%3E lastResult = matchResult.get();%0A    Object lastId = lastResult != null ? lastResult.first : null;%0A    int lastOffset = lastResult != null ? lastResult.second : Integer.MAX_VALUE;%0A    MatchData lastMatch = lastResult != null ? lastResult.third : MatchData.NOT_MATCHED;%0A%0A
     if (last
@@ -1968,24 +1968,65 @@
 teOffset) %7B%0A
+        checkMatched(lastMatch, string);%0A
         retu
@@ -2404,33 +2404,521 @@
 ED;%0A    
-return lastMatch;
+checkMatched(lastMatch, string);%0A    matchResult.set(new Trinity%3C%3E(lastId, lastOffset, lastMatch));%0A    return lastMatch;%0A  %7D%0A%0A  private static void checkMatched(MatchData match, StringWithId string) %7B%0A    if (match.matched() && match.byteOffset().getEndOffset() %3E string.bytes.length) %7B%0A      throw new IllegalStateException(%0A        %22Match data out of bounds: %22 + match.byteOffset().getStartOffset() + %22 %3E %22 + string.bytes.length + %22%5Cn%22 +%0A        new String(string.bytes, StandardCharsets.UTF_8));%0A    %7D
 %0A  %7D%0A%0A  

make regex cache thread local to prevent race problems with caching last match (IDEA-219368)