package com.github.bot.curiosone.core.nlp;

import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * Corrects spelling errors in sentences.
 */
public class Spelling {
  /**
   * String representation of the alphabet.
   */
  private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz";

  /**
   * String representation of a Regex to match all letters from 'a' to 'z'.
   */
  private static final String REGEX_A_Z = "[^a-z ]";

  /**
   * The instance of this singleton class.
   */
  private static Spelling instance;

  /**
   * Path to the dictionary file.
   */
  private static String dictionaryPath = "/spelling/dictionary.txt";

  /** Dictionary used in spelling and correction processes. */
  private Map<String, Integer> dict = new HashMap<>();

  /**
   * Constructor of a Spelling Dictionary.
   */
  private Spelling() {
    Path path = null;
    try {
      URL resource = Rule.class.getResource(dictionaryPath);
      path = Paths.get(resource.toURI());
    } catch (URISyntaxException e) {
      e.printStackTrace();
    }

    try {
      String dictStr = new String(Files.readAllBytes(path))
          .toLowerCase()
          .replaceAll(REGEX_A_Z, "");
      Stream.of(dictStr.split(" ")).forEach(word -> {
        dict.compute(word, (k, v) -> v == null ? 1 : v + 1);
      });
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Gets an instance of the spelling dictionary.
   *
   * @return the instance of the spelling dictionary
   */
  public static Spelling getInstance() {
    if  (instance != null) {
      return instance;
    }
    instance = new Spelling();
    return instance;
  }

  /**
   * Tries to correct a mispelled word.
   *
   * @param word the word to be corrected. It should be lowercased.
   * @return the corrected word if the world can be corrected
   */
  public String correct(String word) {
    if (dict.containsKey(word)) {
      return word;
    }
    Optional<String> e1 = known(edits(word))
        .max((a, b) -> dict.get(a) - dict.get(b));

    if (e1.isPresent()) {
      return e1.get();
    }
    return word;
  }

  /**
   * Applies all possible 1 character modifications to a string.
   *
   * @param word the word to modify
   * @return a stream of all generated modified words
   */
  private Stream<String> edits(String word) {
    Stream<String> deletes = IntStream.range(0, word.length())
        .mapToObj(i -> word.substring(0, i) + word.substring(i + 1));

    Stream<String> replaces = IntStream.range(0, word.length())
        .mapToObj(i -> i)
        .flatMap(i -> ALPHABET.chars()
        .mapToObj(c -> word.substring(0, i) + (char) c + word.substring(i + 1)));

    Stream<String> inserts = IntStream.range(0, word.length() + 1)
        .mapToObj(i -> i)
        .flatMap(i -> ALPHABET.chars()
        .mapToObj(c -> word.substring(0, i) + (char) c + word.substring(i)));
    Stream<String> transposes = IntStream.range(0, word.length() - 1)
        .mapToObj(i -> {
          return word.substring(0, i) +  word.substring(i + 1, i + 2)
              + word.charAt(i) + word.substring(i + 2);
        });
    return Stream.of(deletes, replaces, inserts, transposes).flatMap(x -> x);
  }

  /**
   * Verify if the stream of strings is in {@link #dict}.
   *
   * @param words stream of strings
   * @return words that contains only string in {@link #dict} too
   */
  private Stream<String> known(Stream<String> words) {
    return words.filter((word) -> dict.containsKey(word));
  }
}

@@ -877,16 +877,21 @@
 ;%0A%0A  /**
+%0A   *
  Diction
@@ -936,16 +936,19 @@
 ocesses.
+%0A  
  */%0A  pr
@@ -1023,13 +1023,9 @@
 ruct
-or of
+s
  a S
@@ -1623,66 +1623,8 @@
 ets 
-an instance of the spelling dictionary.%0A   *%0A   * @return 
 the 
@@ -1658,16 +1658,17 @@
 ctionary
+.
 %0A   */%0A 
@@ -1868,21 +1868,16 @@
 d word.%0A
-   *%0A
    * @pa
@@ -2308,17 +2308,22 @@
 ossible 
-1
+single
  charact
@@ -2348,21 +2348,16 @@
 o a 
-s
+S
 tring.%0A
-   *%0A
    *
@@ -2385,14 +2385,12 @@
  to 
-modify
+edit
 %0A   
@@ -2408,18 +2408,26 @@
  stream 
-of
+containign
  all gen
@@ -2447,16 +2447,39 @@
 ed words
+ from the original word
 %0A   */%0A 
@@ -3410,23 +3410,37 @@
   * 
-Verify if the s
+Checks whether the provided S
 trea
@@ -3440,25 +3440,25 @@
 d Stream of 
-s
+S
 trings is in
@@ -3471,22 +3471,24 @@
 k #dict%7D
-.%0A   *
+ or not.
 %0A   * @p
@@ -3498,17 +3498,17 @@
 m words 
-s
+S
 tream of
@@ -3508,17 +3508,17 @@
 ream of 
-s
+S
 trings%0A 
@@ -3533,26 +3533,24 @@
 urn 
-words that
+a Stream
  contain
 s on
@@ -3549,16 +3549,45 @@
 tain
-s
+ing
  only 
-s
+the Words represented by a S
 trin
@@ -3608,12 +3608,8 @@
 ict%7D
- too
 %0A   

improve Spelling javadoc