/*
 * @notice
 * 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.elasticsearch.plugin.analysis.icu;

import com.carrotsearch.randomizedtesting.annotations.Listeners;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope.Scope;
import com.carrotsearch.randomizedtesting.annotations.TimeoutSuite;

import org.apache.lucene.tests.util.LuceneTestCase;
import org.apache.lucene.tests.util.TimeUnits;
import org.apache.lucene.util.ArrayUtil;
import org.elasticsearch.test.GraalVMThreadsFilter;
import org.elasticsearch.test.junit.listeners.ReproduceInfoPrinter;
import org.junit.BeforeClass;

import java.util.Locale;

/**
 * @deprecated Remove when IndexableBinaryStringTools is removed.
 */
@Deprecated
@Listeners({ ReproduceInfoPrinter.class })
@ThreadLeakFilters(filters = { GraalVMThreadsFilter.class })
@ThreadLeakScope(Scope.NONE)
@TimeoutSuite(millis = TimeUnits.HOUR)
@LuceneTestCase.SuppressSysoutChecks(bugUrl = "we log a lot on purpose")
public class IndexableBinaryStringToolsTests extends LuceneTestCase {
    private static int NUM_RANDOM_TESTS;
    private static int MAX_RANDOM_BINARY_LENGTH;
    private static final String LINE_SEPARATOR = System.lineSeparator();

    @BeforeClass
    public static void beforeClass() throws Exception {
        NUM_RANDOM_TESTS = atLeast(200);
        MAX_RANDOM_BINARY_LENGTH = atLeast(300);
    }

    public void testSingleBinaryRoundTrip() {
        byte[] binary = new byte[] {
            (byte) 0x23,
            (byte) 0x98,
            (byte) 0x13,
            (byte) 0xE4,
            (byte) 0x76,
            (byte) 0x41,
            (byte) 0xB2,
            (byte) 0xC9,
            (byte) 0x7F,
            (byte) 0x0A,
            (byte) 0xA6,
            (byte) 0xD8 };

        int encodedLen = IndexableBinaryStringTools.getEncodedLength(binary, 0, binary.length);
        char encoded[] = new char[encodedLen];
        IndexableBinaryStringTools.encode(binary, 0, binary.length, encoded, 0, encoded.length);

        int decodedLen = IndexableBinaryStringTools.getDecodedLength(encoded, 0, encoded.length);
        byte decoded[] = new byte[decodedLen];
        IndexableBinaryStringTools.decode(encoded, 0, encoded.length, decoded, 0, decoded.length);

        assertEquals(
            "Round trip decode/decode returned different results:"
                + LINE_SEPARATOR
                + "original: "
                + binaryDump(binary, binary.length)
                + LINE_SEPARATOR
                + " encoded: "
                + charArrayDump(encoded, encoded.length)
                + LINE_SEPARATOR
                + " decoded: "
                + binaryDump(decoded, decoded.length),
            binaryDump(binary, binary.length),
            binaryDump(decoded, decoded.length)
        );
    }

    public void testEncodedSortability() {
        byte[] originalArray1 = new byte[MAX_RANDOM_BINARY_LENGTH];
        char[] originalString1 = new char[MAX_RANDOM_BINARY_LENGTH];
        char[] encoded1 = new char[MAX_RANDOM_BINARY_LENGTH * 10];
        byte[] original2 = new byte[MAX_RANDOM_BINARY_LENGTH];
        char[] originalString2 = new char[MAX_RANDOM_BINARY_LENGTH];
        char[] encoded2 = new char[MAX_RANDOM_BINARY_LENGTH * 10];

        for (int testNum = 0; testNum < NUM_RANDOM_TESTS; ++testNum) {
            int numBytes1 = random().nextInt(MAX_RANDOM_BINARY_LENGTH - 1) + 1; // Min == 1

            for (int byteNum = 0; byteNum < numBytes1; ++byteNum) {
                int randomInt = random().nextInt(0x100);
                originalArray1[byteNum] = (byte) randomInt;
                originalString1[byteNum] = (char) randomInt;
            }

            int numBytes2 = random().nextInt(MAX_RANDOM_BINARY_LENGTH - 1) + 1; // Min == 1

            for (int byteNum = 0; byteNum < numBytes2; ++byteNum) {
                int randomInt = random().nextInt(0x100);
                original2[byteNum] = (byte) randomInt;
                originalString2[byteNum] = (char) randomInt;
            }
            int originalComparison = new String(originalString1, 0, numBytes1).compareTo(new String(originalString2, 0, numBytes2));
            originalComparison = originalComparison < 0 ? -1 : originalComparison > 0 ? 1 : 0;

            int encodedLen1 = IndexableBinaryStringTools.getEncodedLength(originalArray1, 0, numBytes1);
            if (encodedLen1 > encoded1.length) encoded1 = new char[ArrayUtil.oversize(encodedLen1, Character.BYTES)];
            IndexableBinaryStringTools.encode(originalArray1, 0, numBytes1, encoded1, 0, encodedLen1);

            int encodedLen2 = IndexableBinaryStringTools.getEncodedLength(original2, 0, numBytes2);
            if (encodedLen2 > encoded2.length) encoded2 = new char[ArrayUtil.oversize(encodedLen2, Character.BYTES)];
            IndexableBinaryStringTools.encode(original2, 0, numBytes2, encoded2, 0, encodedLen2);

            int encodedComparison = new String(encoded1, 0, encodedLen1).compareTo(new String(encoded2, 0, encodedLen2));
            encodedComparison = encodedComparison < 0 ? -1 : encodedComparison > 0 ? 1 : 0;

            assertEquals(
                "Test #"
                    + (testNum + 1)
                    + ": Original bytes and encoded chars compare differently:"
                    + LINE_SEPARATOR
                    + " binary 1: "
                    + binaryDump(originalArray1, numBytes1)
                    + LINE_SEPARATOR
                    + " binary 2: "
                    + binaryDump(original2, numBytes2)
                    + LINE_SEPARATOR
                    + "encoded 1: "
                    + charArrayDump(encoded1, encodedLen1)
                    + LINE_SEPARATOR
                    + "encoded 2: "
                    + charArrayDump(encoded2, encodedLen2)
                    + LINE_SEPARATOR,
                originalComparison,
                encodedComparison
            );
        }
    }

    public void testEmptyInput() {
        byte[] binary = new byte[0];

        int encodedLen = IndexableBinaryStringTools.getEncodedLength(binary, 0, binary.length);
        char[] encoded = new char[encodedLen];
        IndexableBinaryStringTools.encode(binary, 0, binary.length, encoded, 0, encoded.length);

        int decodedLen = IndexableBinaryStringTools.getDecodedLength(encoded, 0, encoded.length);
        byte[] decoded = new byte[decodedLen];
        IndexableBinaryStringTools.decode(encoded, 0, encoded.length, decoded, 0, decoded.length);

        assertEquals("decoded empty input was not empty", decoded.length, 0);
    }

    public void testAllNullInput() {
        byte[] binary = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

        int encodedLen = IndexableBinaryStringTools.getEncodedLength(binary, 0, binary.length);
        char encoded[] = new char[encodedLen];
        IndexableBinaryStringTools.encode(binary, 0, binary.length, encoded, 0, encoded.length);

        int decodedLen = IndexableBinaryStringTools.getDecodedLength(encoded, 0, encoded.length);
        byte[] decoded = new byte[decodedLen];
        IndexableBinaryStringTools.decode(encoded, 0, encoded.length, decoded, 0, decoded.length);

        assertEquals(
            "Round trip decode/decode returned different results:"
                + LINE_SEPARATOR
                + "  original: "
                + binaryDump(binary, binary.length)
                + LINE_SEPARATOR
                + "decodedBuf: "
                + binaryDump(decoded, decoded.length),
            binaryDump(binary, binary.length),
            binaryDump(decoded, decoded.length)
        );
    }

    public void testRandomBinaryRoundTrip() {
        byte[] binary = new byte[MAX_RANDOM_BINARY_LENGTH];
        char[] encoded = new char[MAX_RANDOM_BINARY_LENGTH * 10];
        byte[] decoded = new byte[MAX_RANDOM_BINARY_LENGTH];
        for (int testNum = 0; testNum < NUM_RANDOM_TESTS; ++testNum) {
            int numBytes = random().nextInt(MAX_RANDOM_BINARY_LENGTH - 1) + 1; // Min == 1

            for (int byteNum = 0; byteNum < numBytes; ++byteNum) {
                binary[byteNum] = (byte) random().nextInt(0x100);
            }

            int encodedLen = IndexableBinaryStringTools.getEncodedLength(binary, 0, numBytes);
            if (encoded.length < encodedLen) encoded = new char[ArrayUtil.oversize(encodedLen, Character.BYTES)];
            IndexableBinaryStringTools.encode(binary, 0, numBytes, encoded, 0, encodedLen);

            int decodedLen = IndexableBinaryStringTools.getDecodedLength(encoded, 0, encodedLen);
            IndexableBinaryStringTools.decode(encoded, 0, encodedLen, decoded, 0, decodedLen);

            assertEquals(
                "Test #"
                    + (testNum + 1)
                    + ": Round trip decode/decode returned different results:"
                    + LINE_SEPARATOR
                    + "  original: "
                    + binaryDump(binary, numBytes)
                    + LINE_SEPARATOR
                    + "encodedBuf: "
                    + charArrayDump(encoded, encodedLen)
                    + LINE_SEPARATOR
                    + "decodedBuf: "
                    + binaryDump(decoded, decodedLen),
                binaryDump(binary, numBytes),
                binaryDump(decoded, decodedLen)
            );
        }
    }

    public String binaryDump(byte[] binary, int numBytes) {
        StringBuilder buf = new StringBuilder();
        for (int byteNum = 0; byteNum < numBytes; ++byteNum) {
            String hex = Integer.toHexString(binary[byteNum] & 0xFF);
            if (hex.length() == 1) {
                buf.append('0');
            }
            buf.append(hex.toUpperCase(Locale.ROOT));
            if (byteNum < numBytes - 1) {
                buf.append(' ');
            }
        }
        return buf.toString();
    }

    public String charArrayDump(char[] charArray, int numBytes) {
        StringBuilder buf = new StringBuilder();
        for (int charNum = 0; charNum < numBytes; ++charNum) {
            String hex = Integer.toHexString(charArray[charNum]);
            for (int digit = 0; digit < 4 - hex.length(); ++digit) {
                buf.append('0');
            }
            buf.append(hex.toUpperCase(Locale.ROOT));
            if (charNum < numBytes - 1) {
                buf.append(' ');
            }
        }
        return buf.toString();
    }
}
