package org.unicode.cldr.draft.keyboard;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.ibm.icu.text.Collator;

/** Builder class to assist in constructing a keyboard object. */
public final class KeyboardBuilder {
  private final ImmutableSet.Builder<KeyboardId> keyboardIds;
  private final ImmutableList.Builder<String> names;
  private final Map<String, String> transformSequenceToOutput;
  private final Table<ModifierKeyCombination, IsoLayoutPosition, CharacterMap>
      modifierAndPositionToCharacter;

  public KeyboardBuilder() {
    keyboardIds = ImmutableSet.builder();
    names = ImmutableList.builder();
    transformSequenceToOutput = Maps.newHashMap();
    modifierAndPositionToCharacter = HashBasedTable.create();
  }

  public KeyboardBuilder addKeyboardIds(Iterable<KeyboardId> keyboardIds) {
    this.keyboardIds.addAll(keyboardIds);
    return this;
  }

  public KeyboardBuilder addName(String name) {
    names.add(name);
    return this;
  }

  public KeyboardBuilder addTransform(String sequence, String output) {
    if (transformSequenceToOutput.containsKey(sequence)
        && !transformSequenceToOutput.get(sequence).equals(output)) {
      String errorMessage = String.format("Duplicate entry for [%s:%s]", sequence, output);
      throw new IllegalArgumentException(errorMessage);
    }
    transformSequenceToOutput.put(sequence, output);
    return this;
  }

  public KeyboardBuilder addCharacterMap(
      ModifierKeyCombination combination, CharacterMap characterMap) {
    checkNotNull(combination);
    if (modifierAndPositionToCharacter.contains(combination, characterMap.position())) {
      CharacterMap existing =
          modifierAndPositionToCharacter.get(combination, characterMap.position());
      checkArgument(
          existing.equals(characterMap),
          "Duplicate entry for [%s:%s:%s]",
          combination,
          characterMap,
          existing);
    }
    modifierAndPositionToCharacter.put(combination, characterMap.position(), characterMap);
    return this;
  }

  public KeyboardBuilder addCharacterMap(
      Collection<ModifierKeyCombination> combinations, CharacterMap characterMap) {
    for (ModifierKeyCombination combination : combinations) {
      addCharacterMap(combination, characterMap);
    }
    return this;
  }

  public ImmutableList<Keyboard> build() {
    ImmutableSet<KeyboardId> keyboardIds = this.keyboardIds.build();
    checkArgument(keyboardIds.size() > 0, "KeyboardIds must contain at least one element");
    // See if key map consolidation is possible.
    ListMultimap<ImmutableSet<CharacterMap>, ModifierKeyCombination> charactersToCombinations =
        ArrayListMultimap.create();
    for (ModifierKeyCombination combination : modifierAndPositionToCharacter.rowKeySet()) {
      Collection<CharacterMap> characterMaps =
          modifierAndPositionToCharacter.row(combination).values();
      charactersToCombinations.put(ImmutableSet.copyOf(characterMaps), combination);
    }
    // Build the key maps.
    KeyboardId id = keyboardIds.iterator().next();
    ImmutableSortedSet.Builder<KeyMap> keyMaps = ImmutableSortedSet.naturalOrder();
    for (ImmutableSet<CharacterMap> characterMaps : charactersToCombinations.keySet()) {
      List<ModifierKeyCombination> combinations = charactersToCombinations.get(characterMaps);
      ModifierKeyCombinationSet combinationSet =
          ModifierKeyCombinationSet.of(ImmutableSet.copyOf(combinations));
      keyMaps.add(KeyMap.of(combinationSet, characterMaps));
    }
    // Add the transforms.
    ImmutableSortedSet.Builder<Transform> transforms =
        ImmutableSortedSet.orderedBy(collatorComparator(Collator.getInstance(id.locale())));
    for (Entry<String, String> transformEntry : transformSequenceToOutput.entrySet()) {
      transforms.add(Transform.of(transformEntry.getKey(), transformEntry.getValue()));
    }
    ImmutableList.Builder<Keyboard> keyboards = ImmutableList.builder();
    for (KeyboardId keyboardId : keyboardIds) {
      keyboards.add(Keyboard.of(keyboardId, names.build(), keyMaps.build(), transforms.build()));
    }
    return keyboards.build();
  }

  public Set<String> transformSequences() {
    return transformSequenceToOutput.keySet();
  }

  private static Comparator<Transform> collatorComparator(final Collator collator) {
    return new Comparator<Transform>() {
      @Override
      public int compare(Transform o1, Transform o2) {
        return collator.compare(o1.sequence(), o2.sequence());
      }
    };
  }
}
