/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.lucene.backward_codecs.lucene87;

import java.util.Arrays;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.lucene.util.SuppressForbidden;

/**
 * This class is a workaround for JDK bug <a
 * href="https://bugs.openjdk.java.net/browse/JDK-8252739">JDK-8252739</a>.
 *
 * @lucene.internal
 */
@FunctionalInterface
interface BugfixDeflater_JDK8252739 {

  public static final boolean IS_BUGGY_JDK = detectBuggyJDK();

  /**
   * Creates a bugfix for {@link Deflater} instances, which works around JDK-8252739.
   *
   * <p>Use this whenever you intend to call {@link Deflater#setDictionary(byte[], int, int)} on a
   * {@code Deflater}.
   */
  @SuppressForbidden(reason = "Works around bug, so it must call forbidden method")
  public static BugfixDeflater_JDK8252739 createBugfix(Deflater deflater) {
    if (IS_BUGGY_JDK) {
      final BytesRefBuilder dictBytesScratch = new BytesRefBuilder();
      return (dictBytes, off, len) -> {
        if (off > 0) {
          dictBytesScratch.grow(len);
          System.arraycopy(dictBytes, off, dictBytesScratch.bytes(), 0, len);
          deflater.setDictionary(dictBytesScratch.bytes(), 0, len);
        } else {
          deflater.setDictionary(dictBytes, off, len);
        }
      };
    } else {
      return deflater::setDictionary;
    }
  }

  /** Call this method as a workaround */
  void setDictionary(byte[] dictBytes, int off, int len);

  @SuppressForbidden(reason = "Detector for the bug, so it must call buggy method")
  private static boolean detectBuggyJDK() {
    final byte[] testData = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
    final byte[] compressed = new byte[32]; // way enough space
    final Deflater deflater = new Deflater(6, true);
    int compressedSize;
    try {
      deflater.reset();
      deflater.setDictionary(testData, 4, 4);
      deflater.setInput(testData);
      deflater.finish();
      compressedSize = deflater.deflate(compressed, 0, compressed.length, Deflater.FULL_FLUSH);
    } finally {
      deflater.end();
    }

    // in nowrap mode we need extra 0-byte as padding, add explicit:
    compressed[compressedSize] = 0;
    compressedSize++;

    final Inflater inflater = new Inflater(true);
    final byte[] restored = new byte[testData.length];
    try {
      inflater.reset();
      inflater.setDictionary(testData, 4, 4);
      inflater.setInput(compressed, 0, compressedSize);
      final int restoredLength = inflater.inflate(restored);
      if (restoredLength != testData.length) {
        return true;
      }
    } catch (
        @SuppressWarnings("unused")
        DataFormatException e) {
      return true;
    } catch (
        @SuppressWarnings("unused")
        RuntimeException e) {
      return true;
    } finally {
      inflater.end();
    }

    if (Arrays.equals(testData, restored) == false) {
      return true;
    }

    // all fine
    return false;
  }
}
