package zemberek.core.enums;

import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;

/**
 * This is a convenience class for enums that also are represented with strings. This classes can be
 * useful for loading enum values from textual data.
 */
public class StringEnumMap<T extends StringEnum> {

  private final ImmutableMap<String, T> map;
  private final Class<T> clazz;
  private boolean ignoreCase;

  private StringEnumMap(Class<T> clazz) {
    this(clazz, true);
  }

  private StringEnumMap(Class<T> clazz, boolean ignoreCase) {
    this.clazz = clazz;
    final ImmutableMap.Builder<String, T> mapBuilder = new ImmutableMap.Builder<>();
    for (T senum : clazz.getEnumConstants()) {
      mapBuilder.put(senum.getStringForm(), senum);
      if (ignoreCase) {
        String lowerCase = senum.getStringForm().toLowerCase(Locale.ENGLISH);
        if (!lowerCase.equals(senum.getStringForm())) {
          mapBuilder.put(lowerCase, senum);
        }
      }
    }
    this.map = mapBuilder.build();
    this.ignoreCase = ignoreCase;
  }

  public static <T extends StringEnum> StringEnumMap<T> get(Class<T> clazz) {
    return new StringEnumMap<>(clazz);
  }

  public T getEnum(String s) {
    if (Strings.isNullOrEmpty(s)) {
      throw new IllegalArgumentException("Input String must have content.");
    }
    T res = map.get(s);
    if (res == null) {
      throw new IllegalArgumentException(
          "Cannot find a representation of :" + s + " for enum class:" + clazz.getName());
    }
    return res;
  }

  public Set<String> getKeysSet() {
    return map.keySet();
  }

  public Collection<T> getEnums(Collection<String> strings) {
    if (strings == null || strings.isEmpty()) {
      return Collections.emptyList();
    } else {
      return Collections2.transform(strings, this::getEnum);
    }
  }

  public boolean enumExists(String s) {
    return ignoreCase ? map.containsKey(s) || map.containsKey(s.toLowerCase(Locale.ENGLISH))
        : map.containsKey(s);
  }
}
