/*
 * Copyright 2014, 2015 Thomas Harning Jr. <harningt@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package us.eharning.atomun.mnemonic.spi;

import com.google.common.annotations.Beta;
import com.google.common.base.Charsets;
import com.google.common.base.Converter;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.LineProcessor;
import com.google.common.io.Resources;

import java.io.IOException;
import java.net.URL;
import java.text.Normalizer;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;

/**
 * Utility class representing a bidirectional map between integer and string.
 *
 * @since 0.1.0
 */
@Beta
@Immutable
public class BidirectionalDictionary extends Converter<Integer, String> {
    private final ImmutableList<String> indexToWordMap;
    private final ImmutableMap<String, Integer> wordToIndexMap;
    private final String wordListIdentifier;

    /**
     * Construct an instance by loading words from a list.
     *
     * @param wordList
     *         list of words to generate the dictionary from.
     * @param wordListIdentifier
     *         associated word list identifier.
     *
     * @since 0.1.0
     */
    public BidirectionalDictionary(@Nonnull List<String> wordList, @Nonnull String wordListIdentifier) {
        this.wordListIdentifier = wordListIdentifier;
        indexToWordMap = ImmutableList.copyOf(wordList);
        ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
        for (int i = 0; i < indexToWordMap.size(); i++) {
            builder.put(indexToWordMap.get(i), i);
        }
        wordToIndexMap = builder.build();
    }

    /**
     * Construct an instance by reading a resource as UTF-8 and line-splitting.
     *
     * @param dictionaryLocation
     *         resource location to generate the dictionary from.
     * @param wordListIdentifier
     *         associated word list identifier.
     *
     * @throws IOException
     *         on an I/O error reading the resource.
     * @since 0.1.0
     */
    public BidirectionalDictionary(@Nonnull URL dictionaryLocation, @Nonnull String wordListIdentifier) throws IOException {
        this(resourceToLines(dictionaryLocation), wordListIdentifier);
    }

    /**
     * Utility method to convert a resource URL into a list of lines.
     *
     * @param resource
     *         location to generate the line list from.
     *
     * @return list of lines.
     *
     * @throws IOException
     *         on I/O error reading from the resource.
     */
    @Nonnull
    private static ImmutableList<String> resourceToLines(@Nonnull URL resource) throws IOException {
        LineProcessor<ImmutableList<String>> lineProcess = new LineProcessor<ImmutableList<String>>() {
            final ImmutableList.Builder<String> result = ImmutableList.builder();

            @Override
            public boolean processLine(@Nonnull String line) throws IOException {
                /* Binding dictionary handling to NFKD normalization */
                line = Normalizer.normalize(line, Normalizer.Form.NFKD);
                result.add(line);
                return true;
            }

            @Override
            public ImmutableList<String> getResult() {
                return result.build();
            }
        };
        return Resources.readLines(resource, Charsets.UTF_8, lineProcess);
    }

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null || getClass() != that.getClass()) {
            return false;
        }
        /* NOTE: Skipping wordToIndexMap due to invariants */
        BidirectionalDictionary thatDictionary = (BidirectionalDictionary) that;
        return Objects.equal(indexToWordMap, thatDictionary.indexToWordMap)
                && Objects.equal(wordListIdentifier, thatDictionary.wordListIdentifier);
    }

    @Override
    public int hashCode() {
        /* NOTE: Skipping wordToIndexMap due to invariants */
        return Objects.hashCode(indexToWordMap, wordListIdentifier);
    }

    /**
     * Returns a representation of {@code a} as an instance of type {@code B}. If {@code a} cannot be
     * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown.
     *
     * @param integer
     *         the instance to convert; will never be null
     *
     * @return the converted instance; <b>must not</b> be null
     */
    @Nonnull
    @Override
    protected String doForward(@Nonnull Integer integer) {
        String result = indexToWordMap.get(integer);
        Preconditions.checkArgument(null != result, "Unknown dictionary index");
        //noinspection ConstantConditions
        return result;
    }

    /**
     * Returns a representation of {@code b} as an instance of type {@code A}. If {@code b} cannot be
     * converted, an unchecked exception (such as {@link IllegalArgumentException}) should be thrown.
     *
     * @param word
     *         the instance to convert; will never be null
     *
     * @return the converted instance; <b>must not</b> be null
     *
     * @throws UnsupportedOperationException
     *         if backward conversion is not implemented; this should be
     *         very rare. Note that if backward conversion is not only unimplemented but
     *         unimplement<i>able</i> (for example, consider a {@code Converter<Chicken, ChickenNugget>}),
     *         then this is not logically a {@code Converter} at all, and should just implement {@link com.google.common.base.Function}.
     */
    @Nonnull
    @Override
    protected Integer doBackward(@Nonnull String word) {
        Integer result = wordToIndexMap.get(word);
        Preconditions.checkArgument(null != result, "Unknown dictionary word");
        //noinspection ConstantConditions
        return result;
    }

    /**
     * Obtains the wordListIdentifier of the dictionary.
     *
     * @return wordListIdentifier.
     *
     * @since 0.1.0
     */
    @Nonnull
    public String getWordListIdentifier() {
        return wordListIdentifier;
    }

    /**
     * Obtains the size of the dictionary.
     *
     * @return size of the dictionary.
     *
     * @since 0.1.0
     */
    public int getSize() {
        return indexToWordMap.size();
    }
}

@@ -3620,32 +3620,197 @@
 s IOException %7B%0A
+                /* Skip comments and empty lines */%0A                if (line.startsWith(%22#%22) %7C%7C line.isEmpty()) %7B%0A                    return true;%0A                %7D%0A
                 

allow comments / empty lines