/**
 * This file is part of Eclipse Steady.
 *
 * Licensed 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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors
 */
package org.eclipse.steady.shared.util;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.validation.constraints.NotNull;

/**
 * Helper methods for string formatting.
 */
public class StringUtil {

  /** Constant <code>KILOBYTE=1024L</code> */
  public static final long KILOBYTE = 1024L;
  /** Constant <code>MEGABYTE=1024L * 1024L</code> */
  public static final long MEGABYTE = 1024L * 1024L;
  /** Constant <code>MILLI_IN_MIN=60L * 1000L</code> */
  public static final long MILLI_IN_MIN = 60L * 1000L;

  /** Constant <code>NANOS_IN_MIN=60L * 1000L * 1000L *1000L</code> */
  public static final long NANOS_IN_MIN = 60L * 1000L * 1000L * 1000L;

  private static final String FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
  private static final SimpleDateFormat FORMAT = new SimpleDateFormat(FORMAT_STRING);

  /**
   * <p>formatMinString.</p>
   *
   * @param _d a double.
   * @return a {@link java.lang.String} object.
   */
  public static final String formatMinString(double _d) {
    return String.format("[%7.1f min]", _d);
  }
  /**
   * <p>formatMinString.</p>
   *
   * @param _n a long.
   * @return a {@link java.lang.String} object.
   */
  public static final String formatMinString(long _n) {
    return formatMinString((double) _n / (double) MILLI_IN_MIN);
  }

  /**
   * <p>formatMBString.</p>
   *
   * @param _d a double.
   * @return a {@link java.lang.String} object.
   */
  public static final String formatMBString(double _d) {
    return String.format("%.2f MB", _d);
  }

  /**
   * <p>formatKBString.</p>
   *
   * @param _d a double.
   * @return a {@link java.lang.String} object.
   */
  public static final String formatKBString(double _d) {
    return String.format("%.2f KB", _d);
  }

  /**
   * Converts byte to Megabyte and returns a string representation in the form "XXXX.Y MB".
   *
   * @param _bytes the number of bytes
   * @see #byteToMBString(long)
   * @return a {@link java.lang.String} object.
   */
  public static final String byteToMBString(double _bytes) {
    return formatMBString(_bytes / (double) MEGABYTE);
  }

  /**
   * <p>byteToKBString.</p>
   *
   * @param _bytes a long.
   * @return a {@link java.lang.String} object.
   */
  public static final String byteToKBString(long _bytes) {
    return formatKBString((double) _bytes / (double) KILOBYTE);
  }

  /**
   * Converts byte to Megabyte and returns a string representation in the form "XXXX.Y MB".
   *
   * @param _bytes the number of bytes
   * @return a {@link java.lang.String} object.
   */
  public static final String byteToMBString(long _bytes) {
    return formatMBString((double) _bytes / (double) MEGABYTE);
  }

  /**
   * <p>formatDate.</p>
   *
   * @param _ms a long.
   * @return a {@link java.lang.String} object.
   */
  public static final synchronized String formatDate(long _ms) {
    return FORMAT.format(new Date(_ms));
  }

  /**
   * Use {@link StringUtil#nanoToFlexDurationString(long)} instead.
   *
   * @param _nano a long.
   * @return a {@link java.lang.String} object.
   */
  @Deprecated
  public static final String nanoToMinString(long _nano) {
    return String.format("[%4.4f min]", (double) _nano / (double) NANOS_IN_MIN);
  }

  /**
   * Returns a string representation of the given nano seconds. In case more than a minute passed, the representation
   * [hhh:mm:ss] will be used, otherwise [ss.SSS ms].
   *
   * @param _nano a long.
   * @return a {@link java.lang.String} object.
   */
  public static final String nanoToFlexDurationString(long _nano) {
    final long h = TimeUnit.NANOSECONDS.toHours(_nano);
    final long m = TimeUnit.NANOSECONDS.toMinutes(_nano) - TimeUnit.HOURS.toMinutes(h);
    final long s =
        TimeUnit.NANOSECONDS.toSeconds(_nano)
            - TimeUnit.HOURS.toSeconds(h)
            - TimeUnit.MINUTES.toSeconds(m);
    final long ms =
        TimeUnit.NANOSECONDS.toMillis(_nano)
            - TimeUnit.HOURS.toMillis(h)
            - TimeUnit.MINUTES.toMillis(m)
            - TimeUnit.SECONDS.toMillis(s);
    if (m > 0) return String.format("[%03d:%02d:%02d]", h, m, s);
    else return String.format("[%02d.%03d ms]", s, ms);
  }

  /**
   * <p>msToMinString.</p>
   *
   * @param _ms a long.
   * @return a {@link java.lang.String} object.
   */
  public static final String msToMinString(long _ms) {
    return String.format("[%4.4f min]", (double) _ms / (double) MILLI_IN_MIN);
  }

  /**
   * <p>join.</p>
   *
   * @param _objs an array of {@link java.lang.Object} objects.
   * @param _sep a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  public static String join(Object[] _objs, String _sep) {
    final List<String> objs = new ArrayList<String>();
    for (Object o : _objs) objs.add(o.toString());
    return StringUtil.join(objs, _sep);
  }

  /**
   * <p>join.</p>
   *
   * @param _strings an array of {@link java.lang.String} objects.
   * @param _sep a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  public static String join(String[] _strings, String _sep) {
    return StringUtil.join(Arrays.asList(_strings), _sep);
  }

  /**
   * Joins a {@link List} of {@link String}s.
   *
   * @param _strings a {@link java.util.List} object.
   * @param _sep a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  /*public static String join(List<String> _strings, String _sep) {
  	final StringBuilder b = new StringBuilder();
  	int i = 0;
  	for(String p: _strings) {
  		if(i++>0) b.append(_sep);
  		b.append(p.toString());
  	}
  	return b.toString();
  }*/

  /**
   * Joins a {@link Set} of {@link String}s.
   *
   * @param _strings a {@link java.util.Set} object.
   * @param _sep a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  /*public static String join(Set<String> _strings, String _sep) {
  	final StringBuilder b = new StringBuilder();
  	int i = 0;
  	for(String p: _strings) {
  		if(i++>0) b.append(_sep);
  		b.append(p.toString());
  	}
  	return b.toString();
  }*/

  /**
   * Joins a {@link Collection} of {@link Object}s into a {@link String} by calling {@link Object#toString()}.
   *
   * @param _objs a collection of {@link java.lang.Object} objects.
   * @param _sep a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  public static String join(Collection<?> _objs, String _sep) {
    final StringBuilder b = new StringBuilder();
    int i = 0;
    for (Object o : _objs) {
      if (i++ > 0) b.append(_sep);
      b.append(o.toString());
    }
    return b.toString();
  }

  /**
   * <p>padLeft.</p>
   *
   * @param _value a {@link java.lang.String} object.
   * @param _length a int.
   * @return a {@link java.lang.String} object.
   */
  public static String padLeft(String _value, int _length) {
    return String.format("%1$" + _length + "s", _value);
  }
  /**
   * <p>padLeft.</p>
   *
   * @param _value a long.
   * @param _length a int.
   * @return a {@link java.lang.String} object.
   */
  public static String padLeft(long _value, int _length) {
    return padLeft(Long.toString(_value), _length);
  }
  /**
   * <p>padLeft.</p>
   *
   * @param _value a int.
   * @param _length a int.
   * @return a {@link java.lang.String} object.
   */
  public static String padLeft(int _value, int _length) {
    return padLeft(Integer.toString(_value), _length);
  }
  /**
   * <p>padLeft.</p>
   *
   * @param _value a boolean.
   * @param _length a int.
   * @return a {@link java.lang.String} object.
   */
  public static String padLeft(boolean _value, int _length) {
    return padLeft(Boolean.toString(_value), _length);
  }
  /**
   * <p>padLeft.</p>
   *
   * @param _value a double.
   * @param _length a int.
   * @return a {@link java.lang.String} object.
   */
  public static String padLeft(double _value, int _length) {
    return padLeft(Double.toString(_value), _length);
  }

  /**
   * Returns true if the given {@link String} is not null and its length is shorter or equal to the given number.
   * Throws an {@link IllegalArgumentException} otherwise.
   *
   * @param _string a {@link java.lang.String} object.
   * @param _length a int.
   * @return a boolean.
   * @throws java.lang.IllegalArgumentException if any.
   */
  public static boolean meetsLengthConstraint(String _string, int _length)
      throws IllegalArgumentException {
    if (_string == null) throw new IllegalArgumentException("String argument is null");
    else if (_string.length() > _length)
      throw new IllegalArgumentException(
          "String argument exceeds the length of [" + _length + "] characters");
    else return true;
  }

  /**
   * <p>getRandonString.</p>
   *
   * @param _length a int.
   * @return a {@link java.lang.String} object.
   */
  public static final String getRandonString(int _length) {
    final String rnd = Double.toString(Math.random());
    return rnd.substring(2, Math.min(_length + 2, rnd.length()));
  }

  /**
   * Removes square brackets (if any), splits the given String using comma (,) and returns an array of trimmed values.
   *
   * @param _string a {@link java.lang.String} object.
   * @return an array of {@link java.lang.String} objects.
   */
  public static String[] toArray(@NotNull String _string) {
    if (_string.startsWith("[") && _string.endsWith("]"))
      _string = _string.substring(1, _string.length() - 1);
    final String[] values = _string.split(",");
    for (int i = 0; i < values.length; i++) values[i] = values[i].trim();
    return values;
  }

  /**
   * Returns true of the given arrayis null, empty or contains only one element that equals an empty {@link String}.
   *
   * @param _array an array of {@link java.lang.String} objects.
   * @return a boolean.
   */
  public static boolean isEmptyOrContainsEmptyString(String[] _array) {
    return _array == null || _array.length == 0 || (_array.length == 1 && _array[0].equals(""));
  }

  /**
   * Returns true if the given String matches to one of the provided {@link Pattern}s, false otherwise.
   *
   * @param _string a {@link java.lang.String} object.
   * @param _patterns an array of {@link java.util.regex.Pattern} objects.
   * @return a boolean.
   */
  public static boolean matchesPattern(String _string, Pattern[] _patterns) {
    if (_string == null) return false;
    for (Pattern p : _patterns) {
      final Matcher m = p.matcher(_string);
      if (m.matches()) {
        return true;
      }
    }
    return false;
  }
}
