/**
 * The MIT License
 * Copyright © 2020 Thorsten Schulz <http://javacomm.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package net.javacomm.schulz;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Random;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;




/*****************************************************************************/
/*                                                                           */
/* Class       : Functions                                                   */
/* Owner       : Thorsten Schulz                                             */
/* License     : All rights reserved                                         */
/* Createtd    : 04.08.2002 by Thorsten Schulz, TSchulz@javacomm.net         */
/* Last Update : 17.04.2020 by Thorsten Schulz                               */
/*                                                                           */
/*****************************************************************************/

public final class Functions {

  private final static Logger log = LogManager.getLogger(Functions.class);

  private Functions() {}



  public static String getEasterDate(int year) {
    String month = new String("");
    int a, b, c, d, e, k, p, q, m, n, ostern;
    a = year % 19;
    b = year % 4;
    c = year % 7;
    k = year / 100;
    p = (8 * k + 13) / 25;
    q = k / 4;
    m = (15 + k - p - q) % 30;
    n = (4 + k - q) % 7;
    d = (19 * a + m) % 30;
    e = (2 * b + 4 * c + 6 * d + n) % 7;
    ostern = 22 + d + e;
    if ((d + e) == 35) {
      ostern = 50;
    }
    if (d == 28) {
      if (e == 6) {
        if ((11 * m + 11) % 30 < 19) {
          ostern = 49;
        }
      }
    }
    if (ostern > 31) {
      ostern = ostern - 31;
      month = "April";
      return (String.valueOf(ostern) + "." + month);
    }
    month = "März";
    return (String.valueOf(ostern) + "." + month);
  }



  public static int intSqrt(int value) {
    float floatSqrt;
    int roundSqrt;
    Integer root = Integer.valueOf(value);
    double dValue = root.doubleValue();
    Double solution = Double.valueOf(Math.sqrt(dValue));
    floatSqrt = solution.floatValue();
    roundSqrt = Math.round(floatSqrt);
    return (roundSqrt);
  }



  public static boolean isPrimeNumber(int value) {

    int modular;
    boolean prime = false;

    if (value == 1) {
      prime = false;
    }
    else if (value == 2) {
      prime = true;
    }
    else if (value == 0) {
      prime = false;
    }
    else if (value < 0) {
      prime = isPrimeNumber(Math.abs(value));
    }
    else {

      for (int x = 2; x <= intSqrt(value); x++) {
        modular = value % x;
        if (modular == 0) {
          prime = false;
          break;
        }
        prime = true;
      }
    }
    return (prime);
  }



  public static synchronized String getPrimeFactors(int value) {
    value = Math.abs(value);
    int quotient = 0;
    int rest;
    String primeFactors = new String("");
    if (isPrimeNumber(value)) {
      primeFactors = String.valueOf(value);
      value = 1;
    }
    if (value >= 2) {
      for (int divisor = 2; divisor <= value; divisor++) {
        if (log.isInfoEnabled()) log.info("Divisor ist:" + divisor);
        if (isPrimeNumber(value)) {
          divisor = value;
        }
        if (isPrimeNumber(divisor)) {
          do {
            rest = value % divisor;
            if (rest == 0) {
              quotient = value / divisor;
              value = quotient;
              if (primeFactors.equals("")) {
                primeFactors = String.valueOf(divisor);
              }
              else {
                primeFactors += "*";
                primeFactors += String.valueOf(divisor);
              }
            }
          }
          while (rest == 0);
        }
      }
    }
    return (primeFactors);
  }



  public static synchronized int getNumberOfFactors(int value) {
    value = Math.abs(value);
    int quotient = 0;
    int rest;
    int numberOfFactors = 0;
    if (isPrimeNumber(value)) {
      numberOfFactors++;
      value = 1;
    }
    if (value >= 2) {
      for (int divisor = 2; divisor <= value; divisor++) {
        if (log.isInfoEnabled()) log.info("Divisor =" + divisor);
        if (isPrimeNumber(value)) {
          divisor = value;
        }
        if (isPrimeNumber(divisor)) {
          do {
            rest = value % divisor;
            if (rest == 0) {
              quotient = value / divisor;
              value = quotient;
              numberOfFactors++;
            }
          }
          while (rest == 0);
        }
      }
    }
    return (numberOfFactors);
  }



  public static int getNumber(String value) {
    Integer number = Integer.valueOf(0);
    try {
      number = Integer.valueOf(value);
    }
    catch (NumberFormatException e) {
      log.warn("This String has no number!");
    }
    return (number.intValue());
  }



  public static int[] getIntArrayOfDiceRolls(int numberOfDiceSides, int numberOfDice) {
    int[] diceNumeralMemory = new int[numberOfDice];
    // erzeuge einen neuen Zufallsgenerator mit der aktuellen Zeit als Initialwert
    Random random = new Random(System.currentTimeMillis());
    for (int x = 0; x < diceNumeralMemory.length; x++) {
      diceNumeralMemory[x] = random.nextInt(numberOfDiceSides) + 1;
    }

    return (diceNumeralMemory);
  }



  public static int summeOfIntArray(int[] intArray) {
    int summe = 0;

    for (int k = 0; k < intArray.length; k++) {
      summe += intArray[k];
    }
    return (summe);
  }



  public static int getSummeOfDiceRoll(int numberOfDiceSides, int numberOfDice) {
    int summe = 0;
    int[] arrayOfDiceRolls = getIntArrayOfDiceRolls(numberOfDiceSides, numberOfDice);
    summe = summeOfIntArray(arrayOfDiceRolls);
    return (summe);
  }



  public static boolean checkIfIntIsPositive(int numeral) {
    boolean intIsPositive = true;

    if (numeral < 0) {
      intIsPositive = false;
    }
    return (intIsPositive);
  }



  public static String removeSpecialSignsOfString(String text) {
    String txt = new String("");
    String solutionText = new String("");
    txt = "ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß0123456789";
    char[] legalSigns = new char[txt.length()];
    char[] stringSigns = new char[text.length()];
    legalSigns = txt.toCharArray();
    stringSigns = text.toCharArray();

    for (int y = 0; y < stringSigns.length; y++) {
      for (int x = 0; x < legalSigns.length; x++) {
        if (stringSigns[y] == legalSigns[x]) {
          solutionText += stringSigns[y];
          break;
        }
      }
    }
    return (solutionText);
  }



  public static String removeUmlauteOfString(String loweredCasedString) {
    String solutionText = new String("");
    char[] lettersOfString = new char[loweredCasedString.length()];

    lettersOfString = loweredCasedString.toCharArray();

    for (int x = 0; x < lettersOfString.length; x++) {
      switch(lettersOfString[x]) {
        case 'ä': {
          solutionText += "ae";
        }
          break;
        case 'ö': {
          solutionText += "oe";
        }
          break;
        case 'ü': {
          solutionText += "ue";
        }
          break;
        case 'ß': {
          solutionText += "ss";
        }
          break;
        case 'à': {
          solutionText += "a";
        }
          break;
        case 'á': {
          solutionText += "a";
        }
          break;
        case 'â': {
          solutionText += "a";
        }
          break;
        case 'ã': {
          solutionText += "a";
        }
          break;
        case 'å': {
          solutionText += "a";
        }
          break;
        case 'ç': {
          solutionText += "c";
        }
          break;
        case 'è': {
          solutionText += "e";
        }
          break;
        case 'é': {
          solutionText += "e";
        }
          break;
        case 'ê': {
          solutionText += "e";
        }
          break;
        case 'ë': {
          solutionText += "e";
        }
          break;
        case 'ì': {
          solutionText += "i";
        }
          break;
        case 'í': {
          solutionText += "i";
        }
          break;
        case 'î': {
          solutionText += "i";
        }
          break;
        case 'ï': {
          solutionText += "i";
        }
          break;
        case 'ñ': {
          solutionText += "n";
        }
          break;
        case 'ò': {
          solutionText += "o";
        }
          break;
        case 'ó': {
          solutionText += "o";
        }
          break;
        case 'ô': {
          solutionText += "o";
        }
          break;
        case 'õ': {
          solutionText += "o";
        }
          break;
        case 'ø': {
          solutionText += "o";
        }
          break;
        case 'ù': {
          solutionText += "u";
        }
          break;
        case 'ú': {
          solutionText += "u";
        }
          break;
        case 'û': {
          solutionText += "u";
        }
          break;
        case 'ý': {
          solutionText += "y";
        }
          break;
        case 'ÿ': {
          solutionText += "y";
        }
          break;
        default: {
          solutionText += lettersOfString[x];
        }
          break;
      }
    }
    return (solutionText);
  }



  /**
   *
   *
   * @param text
   * @return
   */
  public static String prepareStringToCompare(String text) {
    String solutionText = new String("");
    text = text.toLowerCase();
    text = removeUmlauteOfString(text);
    solutionText = removeSpecialSignsOfString(text);
    return (solutionText);
  }



  /**
   * Finde einen String in einem anderen String. Bei der Suche werden vorhandene
   * Sonderzeichen im Referenzstring ausgeblendet.
   *
   * @param testString
   *                        der zu prüfende String
   * @param referenceString
   *                        in diesem String wird gesucht
   * @return {@code true}, der testString war im Referenzstring enthalten
   */
  public static boolean isStringInString(String testString, String referenceString) {
    if ((testString == null) && (referenceString == null)) return true;
    else if ((testString == null) && (referenceString != null)) return false;
    else if ((testString != null) && (referenceString == null)) return false;
    boolean equal = false;
    int difference = 0;
    String subString = new String("");

    if (stringIsEmpty(testString)) {
      return (equal);
    }
    if (stringIsEmpty(referenceString)) {
      return (equal);
    }

    testString = prepareStringToCompare(testString);
    referenceString = prepareStringToCompare(referenceString);

    if (stringIsEmpty(testString)) {
      return (equal);
    }
    if (stringIsEmpty(referenceString)) {
      return (equal);
    }

    if (testString.equals(referenceString)) {
      equal = true;
    }
    else if (testString.length() < referenceString.length()) {
      difference = referenceString.length() - testString.length();

      for (int x = 0; x < difference + 1; x++) {
        subString = referenceString.substring(x, x + testString.length());
        if (log.isDebugEnabled()) log.debug(subString);
        if (testString.equals(subString)) {
          equal = true;
          break;
        }
      }
    }
    return (equal);
  }



  public static boolean stringIsEmpty(String checkString) {
    boolean empty = false;
    if (checkString.length() == 0) {
      empty = true;
    }
    return (empty);
  }



  public static AbilityCheckDatas getAbilityCheckDatas(String abilityValue, String modificatorValue) {

    int[] diceRoll;
    int ability = getNumber(abilityValue);
    int modificator = getNumber(modificatorValue);
    int control = 0;
    int chanceCritical = 0;
    int summe = 0;
    int effect = 0;
    boolean critical = false;
    AbilityCheckDatas abilityCheckDatas = new AbilityCheckDatas();

    if (ability >= 1) {
      abilityCheckDatas.initValues();
      control = ability + modificator;
      diceRoll = getIntArrayOfDiceRolls(20, 1);
      if (diceRoll[0] == 20) {
        if (ability > 19) {
          chanceCritical = ability / 20;
          summe = getSummeOfDiceRoll(20, chanceCritical);
          if (log.isInfoEnabled()) log.info(Integer.toString(summe));
          if (chanceCritical == summe) {
            critical = true;
            effect = Math.round(1f / ability * 100f * (-1f));
            abilityCheckDatas.setDieValue(diceRoll[0]);
            abilityCheckDatas.setEffectValue(effect);
            abilityCheckDatas.setCritical(critical);
          }
          else { // chanceCritical != summe
            effect = control - diceRoll[0];
            abilityCheckDatas.setDieValue(diceRoll[0]);
            abilityCheckDatas.setEffectValue(effect);
            abilityCheckDatas.setCritical(critical);
          }
        }
        else { // Ability <= 19
          critical = true;
          effect = Math.round(1f / ability * 100f * (-1f));
          abilityCheckDatas.setDieValue(diceRoll[0]);
          abilityCheckDatas.setEffectValue(effect);
          abilityCheckDatas.setCritical(critical);
        }
      }
      else { // diceRoll[0] != 20
        if (diceRoll[0] == 1) {
          critical = true;
          effect = control - diceRoll[0] + 10;
          abilityCheckDatas.setDieValue(diceRoll[0]);
          abilityCheckDatas.setEffectValue(effect);
          abilityCheckDatas.setCritical(critical);
        }
        else { // diceRoll[0] != 1
          effect = control - diceRoll[0];
          abilityCheckDatas.setDieValue(diceRoll[0]);
          abilityCheckDatas.setEffectValue(effect);
          abilityCheckDatas.setCritical(critical);
        }
      }
    }
    else { // ability < 1
      abilityCheckDatas.setValuesOnError();
    }
    return (abilityCheckDatas);
  }



  /**
   * Gibt das Maximum des Int- Arrays zurück.
   */

  public static int getMaxOfIntArray(int[] array) {
    int max = array[0];

    for (int y = 0; y < array.length; y++) {
      if (max < array[y]) {
        max = array[y];
      }
    }
    return (max);
  }



  /**
   * Gibt das Minimum des Int- Arrays zurück.
   */

  public static int getMinOfIntArray(int[] array) {
    int min = array[0];

    for (int y = 0; y < array.length; y++) {
      if (min > array[y]) {
        min = array[y];
      }
    }
    return (min);
  }



  /**
   * Gibt alle Teilstrings als Array zurück, die durch sign im String getrennt
   * sind.
   */

  public static String[] getAllTasksFromString(String taskCode, char sign) {
    int count = 0;
    int number = 0;
    int start = 0;
    int end = 1;
    String[] task;

    if (stringIsEmpty(taskCode)) {
      task = new String[1];
      task[0] = new String("");
      return (task);
    }

    if (stringIsIllegal(taskCode, sign)) {
      task = new String[1];
      task[0] = new String("");
      return (task);
    }

    String str = taskCode;
    char ch = sign;
    char[] letters;
    task = new String[getNumberOfSeparators(str, ch) + 1];
    for (int y = 0; y < task.length; y++) {
      task[y] = new String("");
    }

    letters = str.toCharArray();

    do {
      if (letters[count] == ch) {
        end = count;
        task[number] = str.substring(start, end);
        count++;
        start = count;
        if (log.isDebugEnabled()) log.debug("task[" + number + "] =" + task[number]);
        number++;
      }
      count++;
    }
    while (count < str.length());
    end = count;
    task[number] = str.substring(start, end);
    if (log.isDebugEnabled()) log.debug("task[" + number + "] =" + task[number]);
    return (task);
  }



  public static int getNumberOfSeparators(String s, char c) {
    int numberOfString = 0;
    String check = s;
    char separator = c;
    char[] charactersOfString = check.toCharArray();
    if (stringIsEmpty(check)) {
      return (0);
    }
    for (int x = 0; x < charactersOfString.length; x++) {
      if (charactersOfString[x] == separator) {
        numberOfString++;
      }
    }
    return (numberOfString);
  }



  public static boolean stringIsIllegal(String str, char sign) {
    boolean illegal = false;
    char[] check = str.toCharArray();
    for (int c = 0; c < check.length; c++) {
      if (check[c] == sign) {
        if (c < check.length) {
          if (check[c + 1] == sign) {
            illegal = true;
            if (log.isInfoEnabled()) log.info("Two signs of " + sign + " are not allowed!!!");
            break;
          }
        }
      }
    }
    return (illegal);
  }



  /**
   * Eine rationale Zahl wird in eine ganze Zahl umgewandelt und als
   * {@code String} zurück gegeben. {@code double value} muss wirklich ganzzahlig
   * sein, um gewandelt zu werden.
   *
   * @param value
   *              eine rationale Zahl
   * @return {@code value} als ganze Zahl
   * @throws NumberOutOfRangeException
   *                                   {@code value} liegt nicht im Wertebereich
   *                                   von int
   * @throws NoIntegerException
   *                                   {@code value} ist nicht ganzzahlig
   */
  public static String doubleToIntegerString(double value)
      throws NumberOutOfRangeException, NoIntegerException {
    double number = Math.rint(value);
    if (number != value) {
      throw new NoIntegerException(String.valueOf(value) + " ist nicht ganzzahlig");
    }
    if (number > Integer.MAX_VALUE) throw new NumberOutOfRangeException("value>Integer.MAX_VALUE");
    if (number < Integer.MIN_VALUE) throw new NumberOutOfRangeException("value<Integer.MIN_VALUE");
    int intValue = (int) number;
    return String.valueOf(intValue);
  }



  public static double getDoubleNumber(String value) {
    Double number = Double.valueOf(0d);
    try {
      number = Double.valueOf(value);
    }
    catch (NumberFormatException e) {
      log.warn("This String has no number!");
    }
    return (number.doubleValue());
  }



  public static Calendar getDateValue(String dateValue) throws ParseException {
    SimpleDateFormat simpleDate = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);
    Date simpleDateValue = simpleDate.parse(dateValue);

    Calendar calendar = Calendar.getInstance();

    calendar.setTime(simpleDateValue);

    return (calendar);
  }

}