
// HunspellChecker.java
// Andrew Davison, ad@coe.psu.ac.th, Dec. 2022

/* Use Hunspell to check a word, a string of words, or
   a file of text. 


   Using Ole Schönburg's hunspell-java,
   Java bindings to Hunspell
   https://gitlab.com/dumonts/hunspell-java

   It includes the Hunspell library, but not dictionaries.
   https://hunspell.github.io/

   JARs available at:
   https://jar-download.com/artifact-search/hunspell

  Usage:
    > compile HunspellChecker.java
    > run HunspellChecker <fnm>
    e.g.
    > run HunspellChecker testSpell1.txt

*/

import java.io.*;
import java.nio.file.*;
import java.util.*;

import dumonts.hunspell.Hunspell;


public class HunspellChecker
{
  private static final String DICT = "dicts/en_US.dic";
  private static final String DICT_TREE = "dicts/en_US.aff";

  private static final String LARGE_DICT = "dicts/en_US-large.dic";
  private static final String LARGE_DICT_TREE = "dicts/en_US-large.aff";
   /* From:
 https://sourceforge.net/projects/wordlist/files/speller/2020.12.07/
   */


  private Hunspell hs;


  public HunspellChecker()
  { this(false);  }


  public HunspellChecker(boolean isLarge)
  {
    if (isLarge) {
      hs = new Hunspell(Paths.get(LARGE_DICT), Paths.get(LARGE_DICT_TREE));
      System.out.println("Loaded large Hunspell");
    }
    else {
      hs = new Hunspell(Paths.get(DICT), Paths.get(DICT_TREE));
      System.out.println("Loaded Hunspell");
    }
  }  // end of HunspellChecker()



  public void add(String word)
  // only added to this instance, not to the dictionary
  {  
    hs.add(word);
    System.out.println("Added \"" + word + "\" to Hunspell");
  }


  public void close()
  {   hs.close();  }


  public boolean isCorrect(String word)
  {  return hs.spell(word);  }


  public String suggest(String word)
  // return the first suggested word (if there is one)
  {
    String[] suggestions = hs.suggest(word);
    if (suggestions == null)
      return null;
    else {
      try {
        return suggestions[0];  // sometimes fails ?
      }
      catch(ArrayIndexOutOfBoundsException e)
      {  return null; }
    }
  }  // end of suggest()



  public void checkWord(String word)
  {  checkWord(word, -1, -1);  }


  private void checkWord(String word, int lineNo, int wordNo)
  /* The lineNo and wordNo specify the location of the word
     in a string or a file. They are set to -1 if not being
     used.
  */
  {
    if (!hs.spell(word)) {
      String[] suggestions = hs.suggest(word);

      if (lineNo != -1)
        System.out.print("Line " + lineNo + ": ");
      if (wordNo != -1)
        System.out.print("Word " + wordNo + ": ");

      if (suggestions.length == 1)
        System.out.println("Replace \"" +
                       word + "\" with \"" + suggestions[0] + "\"");
      else {
        System.out.println("\"" + word + "\" suggestions:");
        int colCount = 0;
        // print the suggestions in four columns
        for (int i=0; i < suggestions.length-1; i++) {
          System.out.printf(" %2d. %-7s | ", (i+1), suggestions[i]);
          colCount++;
          if (colCount%4 == 0)
            System.out.println();
        }
        System.out.printf(" %2d. %-7s |\n", suggestions.length,
                                suggestions[suggestions.length-1]);
      }
      //System.out.println("-----------");
    }
  }  // end of checkWord()


  public void checkLine(String line)
  {  checkLine(line, -1);  }


  private void checkLine(String line, int lineNo)
  {
    String[] words = line.split("\\s+");
    for (int i = 0; i < words.length; i++) {
     // words[i] = words[i].replaceAll("\\p{Punct}", ""); // strip non-letters
      words[i] = trimPunct(words[i]);
      checkWord(words[i], lineNo, i+1);
    }
  } // end of checkLine()

  
  private String trimPunct(String word)
  /* strip punctuation characters from before and 
     after the string containing the word
  */
  {
    char[] wordChs = word.toCharArray();
    int i = 0;
    while ((i < wordChs.length) &&
           (!Character.isLetterOrDigit(wordChs[i])) ) {
       wordChs[i] = ' ';
       i++;
    }
    i = wordChs.length-1;
    while ((i >= 0) &&
           (!Character.isLetterOrDigit(wordChs[i])) ) {
       wordChs[i] = ' ';
       i--;
    }
    return new String(wordChs).trim();
  }  // trimPunct()


  public void checkFile(String fnm)
  {
    try (BufferedReader br = new BufferedReader( 
                                     new FileReader(fnm))) {
      String line;
      int lineNo = 1;
      while ((line = br.readLine()) != null) {
        checkLine(line, lineNo);
        lineNo++;
      }
    } 
    catch(IOException e)
    {  System.out.println("Unable to read " + fnm);  }
  }  // end of checkFile()


  // --------------------- test rig -------------------

  public static void main(String args[])
  {
    if (args.length != 1) {
      System.out.println("Usage: run HunspellChecker <fnm>");
      System.exit(0);
    }
    String fnm = args[0];

    HunspellChecker hsc = new HunspellChecker();
    hsc.checkFile(fnm);

    hsc.close();
  }  // end of main()

}  // end of HunspellChecker class


