
package de.ivu.elect.dokumente.stimmzettel;

import static java.util.stream.Collectors.toMap;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.isNumeric;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.apache.commons.lang3.StringUtils.length;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

public enum StimmzettelExportCodeFont {

  /**
   * @see https://www.fontpalace.com/font-download/Interleaved+2of5/
   * @see https://de.wikipedia.org/wiki/Strichcode#2/5-Familie
   * @see com.google.zxing.BarcodeFormat#ITF
   */
  BARCODE_2_OF_5_INTERLEAVED("2OF5 Hoch") { // Cf. /kom-all-was-documents/src/main/beispieldokumente/kommunalwahlen/stimmzettel-template-default-resources.dvz!

    @Override
    protected String encodeInternal(String value) {
      return "(" + encodeInternalContent(value) + ")";
    }

    @Override
    protected String encodeInternalContent(String value) {
      String result = trimToEmpty(value);

      if (isNumeric(result)) {
        int length = result.length();

        if (length % 2 != 0) { // Immer gerade Anzahl Ziffern!
          result = leftPad(result, length + 1, getEncodePadCharacter());
        }

        Map<String, String> valueEncodedPerTwoDigits = BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS;

        String resultEncoded = "";

        for (int i = 0; i < result.length(); i += 2) {
          resultEncoded += trimToEmpty(valueEncodedPerTwoDigits.get(result.substring(i, i + 2)));
        }

        result = resultEncoded;
      } else {
        result = "";
      }

      return result;
    }
  },

  /**
   * @see http://www.squaregear.net/fonts/free3of9.shtml
   * @see https://de.wikipedia.org/wiki/Strichcode#Code_39
   * @see com.google.zxing.BarcodeFormat#CODE_39
   */
  BARCODE_3_OF_9("Free 3 of 9") { // Cf. /kom-all-was-documents/src/main/beispieldokumente/kommunalwahlen/stimmzettel-template-default-resources.dvz!

    @Override
    protected String encodeInternal(String value) {
      return "*" + encodeInternalContent(value) + "*";
    }

    @Override
    protected String encodeInternalContent(String value) {
      return trimToEmpty(value);
    }
  },

  /**
   * @see http://www.will-software.com/infos/barcgen.htm
   * @see https://de.wikipedia.org/wiki/Strichcode#Code128
   * @see com.google.zxing.BarcodeFormat#CODE_128
   */
  BARCODE_CODE_128_B_BY_WILL_SOFTWARE("Code-128") { // Cf. /muc-was-documents/src/main/beispieldokumente/code128.ttf!

    @Override
    protected String encodeInternal(String value) {
      return "Á" + encodeInternalContent(value) + "È";
    }

    @Override
    protected String encodeInternalContent(String value) {
      String result = value;

      if (isNotBlank(result)) {
        result += calculateChecksum(result);
      }

      return result;
    }

    private String calculateChecksum(String value) {
      Map<String, Integer> characterCodeDefaultPerCharacter = CODE_128_B_DEFAULT_VALUE_PER_CHARACTER;
      Map<Integer, String> characterPerCharacterCodeDefault = characterCodeDefaultPerCharacter.entrySet().stream().collect(toMap(Entry::getValue, Entry::getKey));

      int startCharacterCode = 104; // == Code B!

      int sumOfStartCharacterAndValueCharactersCodes = startCharacterCode;

      for (int i = 0; i < value.length(); i++) {
        sumOfStartCharacterAndValueCharactersCodes += characterCodeDefaultPerCharacter.get(Character.toString(value.charAt(i))) * (i + 1);
      }

      int checksum = sumOfStartCharacterAndValueCharactersCodes % 103;

      String result = characterPerCharacterCodeDefault.get(checksum);

      if (isBlank(result)) {
        switch (checksum) { // Font spezifisch!!!
          case 0 :
            result = "ß";
            break;

          case 95 :
            result = "´";
            break;
          case 96 :
            result = "ä";
            break;
          case 97 :
            result = "ö";
            break;
          case 98 :
            result = "ü";
            break;
          case 99 :
            result = "Ä";
            break;
          case 100 :
            result = "Ö";
            break;
          case 101 :
            result = "Ü";
            break;
          case 102 :
            result = "µ";
            break;

          default :
            break;
        }
      }

      return result;
    }
  },

  ;

  static final Map<String, String> BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS = new HashMap<>();

  static {
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("00", "0");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("01", "1");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("02", "2");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("03", "3");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("04", "4");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("05", "5");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("06", "6");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("07", "7");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("08", "8");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("09", "9");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("10", ":");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("11", ";");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("12", "<");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("13", "=");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("14", ">");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("15", "?");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("16", "@");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("17", "A");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("18", "B");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("19", "C");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("20", "D");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("21", "E");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("22", "F");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("23", "G");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("24", "H");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("25", "I");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("26", "J");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("27", "K");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("28", "L");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("29", "M");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("30", "N");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("31", "O");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("32", "P");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("33", "Q");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("34", "R");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("35", "S");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("36", "T");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("37", "U");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("38", "V");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("39", "W");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("40", "X");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("41", "Y");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("42", "Z");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("43", "[");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("44", "\\");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("45", "]");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("46", "^");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("47", "_");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("48", "`");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("49", "a");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("50", "À");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("51", "Á");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("52", "Â");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("53", "Ã");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("54", "Ä");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("55", "Å");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("56", "Æ");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("57", "Ç");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("58", "È");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("59", "É");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("60", "Ê");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("61", "Ë");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("62", "Ì");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("63", "Í");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("64", "Î");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("65", "Ï");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("66", "Ð");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("67", "Ñ");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("68", "Ò");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("69", "Ó");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("70", "Ô");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("71", "Õ");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("72", "Ö");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("73", "×");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("74", "Ø");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("75", "Ù");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("76", "Ú");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("77", "Û");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("78", "Ü");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("79", "Ý");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("80", "Þ");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("81", "ß");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("82", "à");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("83", "á");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("84", "â");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("85", "ã");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("86", "ä");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("87", "å");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("88", "æ");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("89", "ç");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("90", "è");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("91", "é");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("92", "ê");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("93", "ë");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("94", "ì");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("95", "í");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("96", "î");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("97", "ï");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("98", "ð");
    BARCODE_2_OF_5_VALUE_PER_TWO_DIGITS.put("99", "ñ");
  }

  static final Map<String, Integer> CODE_128_B_DEFAULT_VALUE_PER_CHARACTER = new HashMap<>();

  static {
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(" ", 0);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("!", 1);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("\"", 2);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("#", 3);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("$", 4);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("%", 5);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("&", 6);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("'", 7);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("(", 8);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(")", 9);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("*", 10);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("+", 11);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(",", 12);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("-", 13);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(".", 14);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("/", 15);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("0", 16);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("1", 17);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("2", 18);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("3", 19);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("4", 20);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("5", 21);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("6", 22);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("7", 23);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("8", 24);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("9", 25);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(":", 26);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(";", 27);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("<", 28);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("=", 29);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put(">", 30);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("?", 31);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("@", 32);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("A", 33);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("B", 34);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("C", 35);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("D", 36);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("E", 37);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("F", 38);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("G", 39);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("H", 40);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("I", 41);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("J", 42);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("K", 43);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("L", 44);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("M", 45);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("N", 46);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("O", 47);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("P", 48);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("Q", 49);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("R", 50);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("S", 51);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("T", 52);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("U", 53);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("V", 54);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("W", 55);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("X", 56);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("Y", 57);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("Z", 58);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("[", 59);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("\\", 60);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("]", 61);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("^", 62);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("_", 63);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("`", 64);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("a", 65);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("b", 66);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("c", 67);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("d", 68);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("e", 69);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("f", 70);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("g", 71);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("h", 72);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("i", 73);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("j", 74);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("k", 75);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("l", 76);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("m", 77);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("n", 78);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("o", 79);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("p", 80);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("q", 81);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("r", 82);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("s", 83);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("t", 84);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("u", 85);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("v", 86);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("w", 87);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("x", 88);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("y", 89);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("z", 90);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("{", 91);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("|", 92);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("}", 93);
    CODE_128_B_DEFAULT_VALUE_PER_CHARACTER.put("~", 94);
  }

  private final String fontFamily;

  private StimmzettelExportCodeFont(String fontFamily) {
    this.fontFamily = fontFamily;
  }

  public String getFontFamily() {
    return fontFamily;
  }

  public String encode(String value) {
    return encode(value, length(value));
  }

  public String encode(String value, long defaultLength) {
    String result = trimToEmpty(value);

    if (isNotBlank(result)) {
      result = leftPad(result, Math.toIntExact(defaultLength), getEncodePadCharacter());
    }

    return encodeInternal(result);
  }

  protected abstract String encodeInternal(String value);

  protected abstract String encodeInternalContent(String value);

  protected Character getEncodePadCharacter() {
    return '0';
  }
}
