import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;

public class MakeDictionaryPlugin implements Plugin<Project> {

  private static <T> T getExtValue(Project project, String key, T defaultValue) {
    if (project.hasProperty(key)) {
      return (T) project.getProperties().get(key);
    } else {
      return defaultValue;
    }
  }

  private static Object getExtValue(Project proj, String key) {
    return getExtValue(proj, key, null);
  }

  private <T> T[] arrayPlus(T[] source, T... addition) {
    List<T> list = new ArrayList<>(Arrays.asList((T[]) source));
    list.addAll(Arrays.asList((T[]) addition));
    return list.toArray(source);
  }

  @Override
  public void apply(Project project) {
    final String languageName = project.getParent().getName();
    final File dictionaryOutputDir = new File(project.getBuildDir(), "dictionary");

    // adding dictionary making tasks
    TaskProvider<MergeWordsListTask> mergingTask =
        project
            .getTasks()
            .register(
                "mergeAllWordLists",
                MergeWordsListTask.class,
                task -> {
                  task.setInputWordsListFiles(new File[0]);
                  task.setOutputWordsListFile(new File(dictionaryOutputDir, "words_merged.xml"));
                  task.setMaxWordsInList(300000);
                });

    TaskProvider<MakeDictionaryTask> makeTask =
        project
            .getTasks()
            .register(
                "makeDictionary",
                MakeDictionaryTask.class,
                task -> {
                  task.dependsOn(mergingTask);
                  task.setInputWordsListFile(new File(dictionaryOutputDir, "words_merged.xml"));
                  task.setPrefix(languageName);
                  task.setResourcesFolder(project.file("src/main/res/"));
                });

    // if AOSP file exists (under language/pack/dictionary/aosp.combined)
    // we'll create the generation task
    // download the words-list from AOSP at
    // https://android.googlesource.com/platform/packages/inputmethods/LatinIME/+/master/dictionaries/
    // make sure that you are using an unzipped file. The XX_wordlist.combined file should be a
    // plain text file.
    // you can also use the GZ version
    if (project.file("dictionary").exists()) {
      File[] aospFiles =
          project.file("dictionary").listFiles((dir, name) -> name.contains(".combined"));
      if (aospFiles != null && aospFiles.length > 0) {
        for (File aospFile : aospFiles) {
          TaskProvider<GenerateWordsListFromAOSPTask> aosp =
              project
                  .getTasks()
                  .register(
                      "parseAospDictionary_" + aospFile.getName().replace('.', '_'),
                      GenerateWordsListFromAOSPTask.class,
                      task -> {
                        task.setInputFile(aospFile);
                        task.setOutputWordsListFile(
                            new File(dictionaryOutputDir, aospFile.getName() + ".xml"));
                        task.setMaxWordsInList(300000);
                      });

          mergingTask.configure(
              task -> {
                task.dependsOn(aosp);
                task.setInputWordsListFiles(
                    arrayPlus(task.getInputWordsListFiles(), aosp.get().getOutputWordsListFile()));
              });
        }
      }

      // you can also provide pre-built word-list XMLs
      if (project.file("dictionary/prebuilt").exists()) {
        File[] prebuiltFiles =
            project.file("dictionary/prebuilt").listFiles((dir, name) -> name.endsWith(".xml"));
        if (prebuiltFiles != null && prebuiltFiles.length > 0) {
          mergingTask.configure(
              task -> {
                task.setInputWordsListFiles(
                    arrayPlus(task.getInputWordsListFiles(), prebuiltFiles));
                System.out.println(
                    "Found prebuilt word-list folder for "
                        + project.getPath()
                        + " with "
                        + prebuiltFiles.length
                        + " files.");
              });
        }
      }

      // we can also parse text files and generate word-list based on that.
      if (project.file("dictionary/inputs").exists()) {
        TaskProvider<GenerateWordsListTask> inputs =
            project
                .getTasks()
                .register(
                    "parseTextInputFiles",
                    GenerateWordsListTask.class,
                    task -> {
                      task.setInputFiles(project.file("dictionary/inputs").listFiles());
                      task.setOutputWordsListFile(new File(dictionaryOutputDir, "inputs.xml"));

                      System.out.println(
                          "Found text inputs for "
                              + project.getPath()
                              + " with "
                              + task.getInputFiles().length
                              + " files.");

                      char[] dictionaryInputPossibleCharacters =
                          getExtValue(project, "dictionaryInputPossibleCharacters", null);
                      if (dictionaryInputPossibleCharacters != null) {
                        task.setWordCharacters(dictionaryInputPossibleCharacters);
                        System.out.println(
                            "Overriding input-text files possible characters to "
                                + new String(dictionaryInputPossibleCharacters));
                      }
                      char[] dictionaryInputAdditionalInnerCharacters =
                          getExtValue(project, "dictionaryInputAdditionalInnerCharacters", null);
                      if (dictionaryInputAdditionalInnerCharacters != null) {
                        task.setAdditionalInnerCharacters(dictionaryInputAdditionalInnerCharacters);
                        System.out.println(
                            "Overriding input-text files possible additional inner characters to "
                                + new String(dictionaryInputAdditionalInnerCharacters));
                      }
                    });

        mergingTask.configure(
            task -> {
              task.dependsOn(inputs);
              task.setInputWordsListFiles(
                  arrayPlus(task.getInputWordsListFiles(), inputs.get().getOutputWordsListFile()));
            });
      }
    }

    project.afterEvaluate(
        evalProject -> {
          if (getExtValue(project, "shouldGenerateDictionary", true)) {
            project
                .getTasks()
                .named("preBuild")
                .configure(preBuildTask -> preBuildTask.dependsOn(makeTask));
          } else {
            makeTask.configure(task -> task.setEnabled(false));
            mergingTask.configure(task -> task.setEnabled(false));
          }
        });
  }
}
