
package de.ivu.elect.exchange.csv.converter;

import static java.util.Collections.unmodifiableList;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.normalizeSpace;
import static org.apache.commons.lang3.StringUtils.split;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.jsefa.common.converter.ConversionException;
import org.jsefa.common.converter.SimpleTypeConverter;
import org.jsefa.common.converter.SimpleTypeConverterConfiguration;

/**
 * Dies ist ein angepasster Boolean-Converter mit folgenden Eigenschaften ...
 * <ul>
 * <li><code>null</code> und leerer String wird zu <code>false</code> konvertiert</li>
 * <li>Format unterstützt mehrere, Pipe-separierte (<code>|</code>) Aliasse für <code>true</code> und <code>false</code> , die als drittes bzw. viertes Element angegeben werden</li>
 * <li>Standard-Format lässt sich per <code>@CsvField(format = {"trueAlias", "falseAlias", "moreTrueAliases", "moreFalseAliases"})</code> überschreiben</li>
 * <li>Standard-Format lässt sich auch nur teilweise überschreiben</li>
 * </ul>
 */
public class ElectCsvBooleanConverter implements SimpleTypeConverter {

  private static final String ALIASES_SEPARATOR = "|";

  private static final String DEFAULT_TRUE_ALIAS = "1";

  private static final String DEFAULT_TRUE_ALIASES = "1|true|wahr|yes|ja|on|an|j";

  private static final String DEFAULT_FALSE_ALIAS = "0";

  private static final String DEFAULT_FALSE_ALIASES = "0|false|falsch|no|nein|off|aus|n";

  private static final String[] DEFAULT_FORMAT = {DEFAULT_TRUE_ALIAS, DEFAULT_FALSE_ALIAS, DEFAULT_TRUE_ALIASES, DEFAULT_FALSE_ALIASES};

  private final List<String> trueAliases;

  private final List<String> falseAliases;

  protected ElectCsvBooleanConverter(SimpleTypeConverterConfiguration configuration) {
    super();

    String[] format = getFormat(configuration);

    this.trueAliases = getAliases(format[0], format[2]);
    this.falseAliases = getAliases(format[1], format[3]);
  }

  protected List<String> getTrueAliases() {
    return unmodifiableList(trueAliases);
  }

  protected List<String> getFalseAliases() {
    return unmodifiableList(falseAliases);
  }

  @Override
  public Boolean fromString(String value) {
    if (isNotBlank(value)) {
      String valueNormalized = normalizeSpace(value);

      if (matchesAlias(getTrueAliases(), valueNormalized)) {
        return Boolean.TRUE;
      }

      if (matchesAlias(getFalseAliases(), valueNormalized)) {
        return Boolean.FALSE;
      }

      throw new ConversionException("Unknown boolean value: " + value);
    }

    return Boolean.FALSE;
  }

  @Override
  public String toString(Object value) {
    if (value == null) {
      return "";
    }

    if (((Boolean) value).booleanValue()) {
      return getTrueAliases().get(0);
    }

    return getFalseAliases().get(0);
  }

  public static ElectCsvBooleanConverter create(SimpleTypeConverterConfiguration configuration) {
    return new ElectCsvBooleanConverter(configuration);
  }

  protected String[] getFormat(SimpleTypeConverterConfiguration configuration) {
    String[] format = configuration.getFormat();
    String[] result = getDefaultFormat();

    if (format == null) {
      return result;
    }

    if (format.length == 1 || format.length == 3 || format.length > 4) {
      throw new ConversionException("The format for a BooleanConverter must be an array with 2 or 4 entries");
    }

    for (int i = 0; i < format.length; i++) {
      result[i] = format[i];
    }

    if (result[0].equals(result[1])) {
      throw new ConversionException("Invalid format for a BooleanConverter: " + result[0] + ", " + result[1]);
    }

    return result;
  }

  protected String[] getDefaultFormat() {
    return ElectCsvBooleanConverter.DEFAULT_FORMAT.clone();
  }

  private List<String> getAliases(String value, String aliases) {
    List<String> result = new ArrayList<>();

    result.add(value);

    for (String alias : split(aliases, ALIASES_SEPARATOR)) {
      if (!result.contains(alias)) {
        result.add(alias);
      }
    }

    return result;
  }

  private boolean matchesAlias(Collection<String> aliases, String value) {
    for (String alias : aliases) {
      if (alias.equalsIgnoreCase(value)) {
        return true;
      }
    }

    return false;
  }
}
