package morfologik.stemming;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;

import morfologik.fsa.FSA;

/**
 * A dictionary combines {@link FSA} automaton and {@link DictionaryMetadata}
 * describing the way terms are encoded in the automaton.
 * 
 * <p>
 * A dictionary consists of two files:
 * <ul>
 * <li>an actual compressed FSA file,
 * <li>{@link DictionaryMetadata}, describing the way terms are encoded.
 * </ul>
 */
public final class Dictionary {
  /**
   * {@link FSA} automaton with the compiled dictionary data.
   */
  public final FSA fsa;

  /**
   * Metadata associated with the dictionary.
   */
  public final DictionaryMetadata metadata;

  /**
   * It is strongly recommended to use static methods in this class for
   * reading dictionaries.
   * 
   * @param fsa
   *            An instantiated {@link FSA} instance.
   * 
   * @param metadata
   *            A map of attributes describing the compression format and
   *            other settings not contained in the FSA automaton. For an
   *            explanation of available attributes and their possible values,
   *            see {@link DictionaryMetadata}.
   */
  public Dictionary(FSA fsa, DictionaryMetadata metadata) {
    this.fsa = fsa;
    this.metadata = metadata;
  }

  /**
   * Attempts to load a dictionary using the path to the FSA file and the
   * expected metadata extension.
   * 
   * @param location The location of the dictionary file (<code>*.dict</code>).
   * @return An instantiated dictionary.
   * @throws IOException if an I/O error occurs.
   */
  public static Dictionary read(Path location) throws IOException {
    final Path metadata = DictionaryMetadata.getExpectedMetadataLocation(location);

    try (InputStream fsaStream = Files.newInputStream(location);
         InputStream metadataStream = Files.newInputStream(metadata)) {
      return read(fsaStream, metadataStream);
    }
  }

  /**
   * Attempts to load a dictionary using the URL to the FSA file and the
   * expected metadata extension.
   * 
   * @param dictURL The URL pointing to the dictionary file (<code>*.dict</code>).
   * @return An instantiated dictionary.
   * @throws IOException if an I/O error occurs.
   */
  public static Dictionary read(URL dictURL) throws IOException {
    final URL expectedMetadataURL;
    try {
      String external = dictURL.toExternalForm();
      expectedMetadataURL = new URL(DictionaryMetadata.getExpectedMetadataFileName(external));
    } catch (MalformedURLException e) {
      throw new IOException("Couldn't construct relative feature map URL for: " + dictURL, e);
    }

    try (InputStream fsaStream = dictURL.openStream();
         InputStream metadataStream = expectedMetadataURL.openStream()) {
      return read(fsaStream, metadataStream);
    }
  }

  /**
   * Attempts to load a dictionary from opened streams of FSA dictionary data
   * and associated metadata. Input streams are not closed automatically.
   * 
   * @param fsaStream The stream with FSA data
   * @param metadataStream The stream with metadata
   * @return Returns an instantiated {@link Dictionary}.
   * @throws IOException if an I/O error occurs.
   */
  public static Dictionary read(InputStream fsaStream, InputStream metadataStream) throws IOException {
    return new Dictionary(FSA.read(fsaStream), DictionaryMetadata.read(metadataStream));    
  }
}
