package com.bbn.bue.common.files;

import com.bbn.bue.common.TextGroupImmutable;
import com.bbn.bue.common.annotations.MoveToBUECommon;
import com.bbn.bue.common.collections.ImmutableListMultitable;
import com.bbn.bue.common.collections.ImmutableMultitable;
import com.bbn.bue.common.collections.ImmutableSetMultitable;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.io.CharSource;
import com.google.common.io.LineProcessor;

import java.io.IOException;
import java.util.List;

/**
 * Loads a {@link com.bbn.bue.common.collections.Multitable} from a {@link CharSource}.
 *
 * The input is required to fall into three fields when split by {@link #fieldSplitter()}
 * (defaults to tab-separated).  The first of these is expected to be the row key, the second
 * the column key, and the third the value.  Multiple values may appear in one value field
 * if the optional {@link #valueListSplitter()} is specified to split them with.
 *
 *
 */
@MoveToBUECommon
@org.immutables.value.Value.Immutable
@TextGroupImmutable
public abstract class MultitableLoader<R, C, V> {
  public abstract Optional<Splitter> valueListSplitter();
  @org.immutables.value.Value.Default
  public Splitter fieldSplitter() {
    return Splitter.on("\t");
  }
  public abstract Function<String, ? extends R> rowInterpreter();
  public abstract Function<String, ? extends C> columnInterpreter();
  public abstract Function<String, ? extends V> valueInterpreter();

  public final ImmutableListMultitable<R, C, V> loadToListMultitable(CharSource source)
      throws IOException {
    final ImmutableListMultitable.Builder<R, C, V> ret =
        ImmutableListMultitable.builder();

    loadToMultitable(source, ret);

    return ret.build();
  }

  public final ImmutableSetMultitable<R, C, V> loadToSetMultitable(CharSource source)
      throws IOException {
    final ImmutableSetMultitable.Builder<R, C, V> ret =
        ImmutableSetMultitable.builder();

    loadToMultitable(source, ret);

    return ret.build();
  }

  public static <R,C,V> MultitableLoader.Builder<R,C,V> builder() {
    return new MultitableLoader.Builder<>();
  }

  public static MultitableLoader.Builder<String, String, String> builderForStrings() {
    return new MultitableLoader.Builder<String,String,String>()
        .rowInterpreter(Functions.<String>identity())
        .columnInterpreter(Functions.<String>identity())
        .valueInterpreter(Functions.<String>identity());
  }

  private void loadToMultitable(final CharSource source,
      final ImmutableMultitable.Builder<R, C, V> ret) throws IOException {
    source.readLines(new LineProcessor<Void>() {
      @Override
      public boolean processLine(final String line) throws IOException {
        final List<String> fields = fieldSplitter().splitToList(line);
        if (fields.size() == 3) {
          final R rowKey = interpret(fields.get(0), rowInterpreter(), "row key", line);
          final C columnKey = interpret(fields.get(1), columnInterpreter(), "column key", line);

          if (valueListSplitter().isPresent()) {
            for (final String value : valueListSplitter().get().split(fields.get(2))) {
              ret.put(rowKey, columnKey, interpret(value, valueInterpreter(), "value", line));
            }
          } else {
            ret.put(rowKey, columnKey, interpret(fields.get(2), valueInterpreter(), "value", line));
          }
        } else {
          throw new IOException("Cannot parse lines as multitable entries:\n" + line);
        }
        // we never stop procesisng lines early
        return true;
      }

      @Override
      public Void getResult() {
        return null;
      }
    });
  }

  private <T> T interpret(String field, Function<String, T> interpreter, String fieldName, String line)
      throws IOException {
    try {
      return interpreter.apply(field);
    } catch (Exception e) {
      throw new IOException("While parsing multitable line\n" + line + "\n failed to interpret " +
          fieldName + " " + field + " using " + interpreter);
    }
  }

  public static class Builder<R,C,V> extends ImmutableMultitableLoader.Builder<R,C,V> {}
}

@@ -312,16 +312,105 @@
 titable;
+%0Aimport com.bbn.bue.common.symbols.Symbol;%0Aimport com.bbn.bue.common.symbols.SymbolUtils;
 %0A%0Aimport
@@ -1156,16 +1156,109 @@
 h.%0A *%0A *
+ Comment lines are currently not skipped, but this could be added as an option in the future.
 %0A */%0A@Mo
@@ -1749,16 +1749,173 @@
 ter();%0A%0A
+  /**%0A   * Loads a %7B@link com.bbn.bue.common.collections.ListMultitable%7D from the specified source%0A   * according to the configuration of this loader.%0A   */%0A
   public
@@ -2179,32 +2179,188 @@
 t.build();%0A  %7D%0A%0A
+  /**%0A   * Loads a %7B@link com.bbn.bue.common.collections.SetMultitable%7D from the specified source%0A   * according to the configuration of this loader.%0A   */%0A
   public final I
@@ -3054,32 +3054,367 @@
 entity());%0A  %7D%0A%0A
+  public static MultitableLoader.Builder%3CSymbol, Symbol, Symbol%3E builderForSymbols() %7B%0A    return new MultitableLoader.Builder%3CSymbol, Symbol, Symbol%3E()%0A        .rowInterpreter(SymbolUtils.symbolizeFunction())%0A        .columnInterpreter(SymbolUtils.symbolizeFunction())%0A        .valueInterpreter(SymbolUtils.symbolizeFunction());%0A  %7D%0A%0A
   private void l
@@ -5030,18 +5030,20 @@
 ilder%3CR,
+ 
 C,
+ 
 V%3E exten
@@ -5085,14 +5085,140 @@
 r%3CR,
+ 
 C,
+ 
 V%3E %7B
+%0A%0A    public Builder%3CR, C, V%3E splitValuesOnCommas() %7B%0A      valueListSplitter(Splitter.on(%22,%22));%0A      return this;%0A    %7D%0A  
 %7D%0A%7D%0A

Improve comments on Multitable loader