/**
 * 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.hadoop.yarn.server.timelineservice.storage.common;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.apache.hadoop.thirdparty.com.google.common.collect.Iterables;
import org.junit.jupiter.api.Test;

import org.apache.hadoop.hbase.util.Bytes;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

public class TestSeparator {

  private static String villain = "Dr. Heinz Doofenshmirtz";
  private static String special =
      ".   *   |   ?   +   \t   (   )   [   ]   {   }   ^   $  \\ \"  %";

  /**
   *
   */
  @Test
  void testEncodeDecodeString() {

    for (Separator separator : Separator.values()) {
      testEncodeDecode(separator, "");
      testEncodeDecode(separator, " ");
      testEncodeDecode(separator, "!");
      testEncodeDecode(separator, "?");
      testEncodeDecode(separator, "&");
      testEncodeDecode(separator, "+");
      testEncodeDecode(separator, "\t");
      testEncodeDecode(separator, "Dr.");
      testEncodeDecode(separator, "Heinz");
      testEncodeDecode(separator, "Doofenshmirtz");
      testEncodeDecode(separator, villain);
      testEncodeDecode(separator, special);

      assertNull(separator.encode(null));

    }
  }

  private void testEncodeDecode(Separator separator, String token) {
    String encoded = separator.encode(token);
    String decoded = separator.decode(encoded);
    String msg = "token:" + token + " separator:" + separator + ".";
    assertEquals(token, decoded, msg);
  }

  @Test
  void testEncodeDecode() {
    testEncodeDecode("Dr.", Separator.QUALIFIERS);
    testEncodeDecode("Heinz", Separator.QUALIFIERS, Separator.QUALIFIERS);
    testEncodeDecode("Doofenshmirtz", Separator.QUALIFIERS, null,
        Separator.QUALIFIERS);
    testEncodeDecode("&Perry", Separator.QUALIFIERS, Separator.VALUES, null);
    testEncodeDecode("the ", Separator.QUALIFIERS, Separator.SPACE);
    testEncodeDecode("Platypus...", (Separator) null);
    testEncodeDecode("The what now ?!?", Separator.QUALIFIERS,
        Separator.VALUES, Separator.SPACE);

  }

  @Test
  void testEncodedValues() {
    testEncodeDecode("Double-escape %2$ and %9$ or %%2$ or %%3$, nor  %%%2$" +
        "= no problem!",
        Separator.QUALIFIERS, Separator.VALUES, Separator.SPACE, Separator.TAB);
  }

  @Test
  void testSplits() {
    byte[] maxLongBytes = Bytes.toBytes(Long.MAX_VALUE);
    byte[] maxIntBytes = Bytes.toBytes(Integer.MAX_VALUE);
    for (Separator separator : Separator.values()) {
      String str1 = "cl" + separator.getValue() + "us";
      String str2 = separator.getValue() + "rst";
      byte[] sepByteArr = Bytes.toBytes(separator.getValue());
      byte[] longVal1Arr = Bytes.add(sepByteArr, Bytes.copy(maxLongBytes,
          sepByteArr.length, Bytes.SIZEOF_LONG - sepByteArr.length));
      byte[] intVal1Arr = Bytes.add(sepByteArr, Bytes.copy(maxIntBytes,
          sepByteArr.length, Bytes.SIZEOF_INT - sepByteArr.length));
      byte[] arr = separator.join(
          Bytes.toBytes(separator.encode(str1)), longVal1Arr,
          Bytes.toBytes(separator.encode(str2)), intVal1Arr);
      int[] sizes = {Separator.VARIABLE_SIZE, Bytes.SIZEOF_LONG,
          Separator.VARIABLE_SIZE, Bytes.SIZEOF_INT};
      byte[][] splits = separator.split(arr, sizes);
      assertEquals(4, splits.length);
      assertEquals(str1, separator.decode(Bytes.toString(splits[0])));
      assertEquals(Bytes.toLong(longVal1Arr), Bytes.toLong(splits[1]));
      assertEquals(str2, separator.decode(Bytes.toString(splits[2])));
      assertEquals(Bytes.toInt(intVal1Arr), Bytes.toInt(splits[3]));

      longVal1Arr = Bytes.add(Bytes.copy(maxLongBytes, 0, Bytes.SIZEOF_LONG -
          sepByteArr.length), sepByteArr);
      intVal1Arr = Bytes.add(Bytes.copy(maxIntBytes, 0, Bytes.SIZEOF_INT -
          sepByteArr.length), sepByteArr);
      arr = separator.join(Bytes.toBytes(separator.encode(str1)), longVal1Arr,
          Bytes.toBytes(separator.encode(str2)), intVal1Arr);
      splits = separator.split(arr, sizes);
      assertEquals(4, splits.length);
      assertEquals(str1, separator.decode(Bytes.toString(splits[0])));
      assertEquals(Bytes.toLong(longVal1Arr), Bytes.toLong(splits[1]));
      assertEquals(str2, separator.decode(Bytes.toString(splits[2])));
      assertEquals(Bytes.toInt(intVal1Arr), Bytes.toInt(splits[3]));

      longVal1Arr = Bytes.add(sepByteArr, Bytes.copy(maxLongBytes,
          sepByteArr.length, 4 - sepByteArr.length), sepByteArr);
      longVal1Arr = Bytes.add(longVal1Arr, Bytes.copy(maxLongBytes, 4, 3 -
          sepByteArr.length), sepByteArr);
      arr = separator.join(Bytes.toBytes(separator.encode(str1)), longVal1Arr,
          Bytes.toBytes(separator.encode(str2)), intVal1Arr);
      splits = separator.split(arr, sizes);
      assertEquals(4, splits.length);
      assertEquals(str1, separator.decode(Bytes.toString(splits[0])));
      assertEquals(Bytes.toLong(longVal1Arr), Bytes.toLong(splits[1]));
      assertEquals(str2, separator.decode(Bytes.toString(splits[2])));
      assertEquals(Bytes.toInt(intVal1Arr), Bytes.toInt(splits[3]));

      arr = separator.join(Bytes.toBytes(separator.encode(str1)),
          Bytes.toBytes(separator.encode(str2)), intVal1Arr, longVal1Arr);
      int[] sizes1 = {Separator.VARIABLE_SIZE, Separator.VARIABLE_SIZE,
          Bytes.SIZEOF_INT, Bytes.SIZEOF_LONG};
      splits = separator.split(arr, sizes1);
      assertEquals(4, splits.length);
      assertEquals(str1, separator.decode(Bytes.toString(splits[0])));
      assertEquals(str2, separator.decode(Bytes.toString(splits[1])));
      assertEquals(Bytes.toInt(intVal1Arr), Bytes.toInt(splits[2]));
      assertEquals(Bytes.toLong(longVal1Arr), Bytes.toLong(splits[3]));

      try {
        int[] sizes2 = {Separator.VARIABLE_SIZE, Separator.VARIABLE_SIZE,
            Bytes.SIZEOF_INT, 7};
        splits = separator.split(arr, sizes2);
        fail("Exception should have been thrown.");
      } catch (IllegalArgumentException e) {
      }

      try {
        int[] sizes2 = {Separator.VARIABLE_SIZE, Separator.VARIABLE_SIZE, 2,
            Bytes.SIZEOF_LONG};
        splits = separator.split(arr, sizes2);
        fail("Exception should have been thrown.");
      } catch (IllegalArgumentException e) {
      }
    }
  }

  /**
   * Simple test to encode and decode using the same separators and confirm that
   * we end up with the same as what we started with.
   *
   * @param token
   * @param separators
   */
  private static void testEncodeDecode(String token, Separator... separators) {
    byte[] encoded = Separator.encode(token, separators);
    String decoded = Separator.decode(encoded, separators);
    assertEquals(token, decoded);
  }

  @Test
  void testJoinStripped() {
    List<String> stringList = new ArrayList<String>(0);
    stringList.add("nothing");

    String joined = Separator.VALUES.joinEncoded(stringList);
    Iterable<String> split = Separator.VALUES.splitEncoded(joined);
    assertTrue(Iterables.elementsEqual(stringList, split));

    stringList = new ArrayList<String>(3);
    stringList.add("a");
    stringList.add("b?");
    stringList.add("c");

    joined = Separator.VALUES.joinEncoded(stringList);
    split = Separator.VALUES.splitEncoded(joined);
    assertTrue(Iterables.elementsEqual(stringList, split));

    String[] stringArray1 = {"else"};
    joined = Separator.VALUES.joinEncoded(stringArray1);
    split = Separator.VALUES.splitEncoded(joined);
    assertTrue(Iterables.elementsEqual(Arrays.asList(stringArray1), split));

    String[] stringArray2 = {"d", "e?", "f"};
    joined = Separator.VALUES.joinEncoded(stringArray2);
    split = Separator.VALUES.splitEncoded(joined);
    assertTrue(Iterables.elementsEqual(Arrays.asList(stringArray2), split));

    List<String> empty = new ArrayList<String>(0);
    split = Separator.VALUES.splitEncoded(null);
    assertTrue(Iterables.elementsEqual(empty, split));

  }

}
