/*
 * 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.tsfile.external.commons.io;

import java.util.Objects;
import java.util.stream.Stream;

/**
 * Enumeration of IO case sensitivity.
 *
 * <p>Different filing systems have different rules for case-sensitivity. Windows is
 * case-insensitive, Unix is case-sensitive.
 *
 * <p>This class captures that difference, providing an enumeration to control how file name
 * comparisons should be performed. It also provides methods that use the enumeration to perform
 * comparisons.
 *
 * <p>Wherever possible, you should use the {@code check} methods in this class to compare file
 * names.
 *
 * @since 1.3
 */
public enum IOCase {

  /** The constant for case-sensitive regardless of operating system. */
  SENSITIVE("Sensitive", true),

  /** The constant for case-insensitive regardless of operating system. */
  INSENSITIVE("Insensitive", false);

  /** Serialization version. */
  private static final long serialVersionUID = -6343169151696340687L;

  /**
   * Factory method to create an IOCase from a name.
   *
   * @param name the name to find
   * @return the IOCase object
   * @throws IllegalArgumentException if the name is invalid
   */
  public static IOCase forName(final String name) {
    return Stream.of(IOCase.values())
        .filter(ioCase -> ioCase.getName().equals(name))
        .findFirst()
        .orElseThrow(() -> new IllegalArgumentException("Illegal IOCase name: " + name));
  }

  /**
   * Tests for cases sensitivity in a null-safe manner.
   *
   * @param ioCase an IOCase.
   * @return true if the input is non-null and {@link #isCaseSensitive()}.
   * @since 2.10.0
   */
  public static boolean isCaseSensitive(final IOCase ioCase) {
    return ioCase != null && ioCase.isCaseSensitive();
  }

  /**
   * Returns the given value if not-null, the defaultValue if null.
   *
   * @param value the value to test.
   * @param defaultValue the default value.
   * @return the given value if not-null, the defaultValue if null.
   * @since 2.12.0
   */
  public static IOCase value(final IOCase value, final IOCase defaultValue) {
    return value != null ? value : defaultValue;
  }

  /** The enumeration name. */
  private final String name;

  /** The sensitivity flag. */
  private final transient boolean sensitive;

  /**
   * Constructs a new instance.
   *
   * @param name the name
   * @param sensitive the sensitivity
   */
  IOCase(final String name, final boolean sensitive) {
    this.name = name;
    this.sensitive = sensitive;
  }

  /**
   * Compares two strings using the case-sensitivity rule.
   *
   * <p>This method mimics {@link String#compareTo} but takes case-sensitivity into account.
   *
   * @param str1 the first string to compare, not null
   * @param str2 the second string to compare, not null
   * @return true if equal using the case rules
   * @throws NullPointerException if either string is null
   */
  public int checkCompareTo(final String str1, final String str2) {
    Objects.requireNonNull(str1, "str1");
    Objects.requireNonNull(str2, "str2");
    return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
  }

  /**
   * Checks if one string ends with another using the case-sensitivity rule.
   *
   * <p>This method mimics {@link String#endsWith} but takes case-sensitivity into account.
   *
   * @param str the string to check
   * @param end the end to compare against
   * @return true if equal using the case rules, false if either input is null
   */
  public boolean checkEndsWith(final String str, final String end) {
    if (str == null || end == null) {
      return false;
    }
    final int endLen = end.length();
    return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
  }

  /**
   * Compares two strings using the case-sensitivity rule.
   *
   * <p>This method mimics {@link String#equals} but takes case-sensitivity into account.
   *
   * @param str1 the first string to compare, not null
   * @param str2 the second string to compare, not null
   * @return true if equal using the case rules
   * @throws NullPointerException if either string is null
   */
  public boolean checkEquals(final String str1, final String str2) {
    Objects.requireNonNull(str1, "str1");
    Objects.requireNonNull(str2, "str2");
    return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
  }

  /**
   * Checks if one string contains another starting at a specific index using the case-sensitivity
   * rule.
   *
   * <p>This method mimics parts of {@link String#indexOf(String, int)} but takes case-sensitivity
   * into account.
   *
   * @param str the string to check, not null
   * @param strStartIndex the index to start at in str
   * @param search the start to search for, not null
   * @return the first index of the search String, -1 if no match or {@code null} string input
   * @throws NullPointerException if either string is null
   * @since 2.0
   */
  public int checkIndexOf(final String str, final int strStartIndex, final String search) {
    final int endIndex = str.length() - search.length();
    if (endIndex >= strStartIndex) {
      for (int i = strStartIndex; i <= endIndex; i++) {
        if (checkRegionMatches(str, i, search)) {
          return i;
        }
      }
    }
    return -1;
  }

  /**
   * Checks if one string contains another at a specific index using the case-sensitivity rule.
   *
   * <p>This method mimics parts of {@link String#regionMatches(boolean, int, String, int, int)} but
   * takes case-sensitivity into account.
   *
   * @param str the string to check, not null
   * @param strStartIndex the index to start at in str
   * @param search the start to search for, not null
   * @return true if equal using the case rules
   * @throws NullPointerException if either string is null
   */
  public boolean checkRegionMatches(
      final String str, final int strStartIndex, final String search) {
    return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
  }

  /**
   * Checks if one string starts with another using the case-sensitivity rule.
   *
   * <p>This method mimics {@link String#startsWith(String)} but takes case-sensitivity into
   * account.
   *
   * @param str the string to check
   * @param start the start to compare against
   * @return true if equal using the case rules, false if either input is null
   */
  public boolean checkStartsWith(final String str, final String start) {
    return str != null
        && start != null
        && str.regionMatches(!sensitive, 0, start, 0, start.length());
  }

  /**
   * Gets the name of the constant.
   *
   * @return the name of the constant
   */
  public String getName() {
    return name;
  }

  /**
   * Does the object represent case-sensitive comparison.
   *
   * @return true if case-sensitive
   */
  public boolean isCaseSensitive() {
    return sensitive;
  }

  /**
   * Replaces the enumeration from the stream with a real one. This ensures that the correct flag is
   * set for SYSTEM.
   *
   * @return the resolved object
   */
  private Object readResolve() {
    return forName(name);
  }

  /**
   * Gets a string describing the sensitivity.
   *
   * @return a string describing the sensitivity
   */
  @Override
  public String toString() {
    return name;
  }
}
