/* LanguageTool, a natural language style checker 
 * Copyright (C) 2021 Daniel Naber (http://www.danielnaber.de)
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */
package org.languagetool.rules.ca;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.languagetool.AnalyzedTokenReadings;
import org.languagetool.AnalyzedToken;
import org.languagetool.rules.RuleMatch;
import org.languagetool.rules.patterns.RuleFilter;
import org.languagetool.synthesis.ca.CatalanSynthesizer;
import org.languagetool.tools.StringTools;

/*
 * Add the pronoun "en" in the required place with all the necessary transformations, 
 * including moving the <marker> positions.
 */

public class AdjustPronounsFilter extends RuleFilter {

  Pattern pApostropheNeeded = Pattern.compile("h?[aeiouàèéíòóú].*", Pattern.CASE_INSENSITIVE);

  static private CatalanSynthesizer synth = CatalanSynthesizer.INSTANCE;

  private static Map<String, String> addEnApostrophe = new HashMap<>();
  static {
    addEnApostrophe.put("m'", "me n'");
    addEnApostrophe.put("t'", "te n'");
    addEnApostrophe.put("s'", "se n'");
    addEnApostrophe.put("ens", "ens n'");
    addEnApostrophe.put("us", "us n'");
    addEnApostrophe.put("vos", "vos n'");
    addEnApostrophe.put("li", "li n'");
    addEnApostrophe.put("els", "els n'");
    addEnApostrophe.put("se m'", "se me n'");
    addEnApostrophe.put("se t'", "se te n'");
    addEnApostrophe.put("se li", "se li n'");
    addEnApostrophe.put("se'ns", "se'ns n'");
    addEnApostrophe.put("se us", "se us n'");
    addEnApostrophe.put("se vos", "se vos n'");
    addEnApostrophe.put("se'ls", "se'ls n'");
    addEnApostrophe.put("hi", "n'hi ");
    addEnApostrophe.put("", "n'");
  }

  private static Map<String, String> addEn = new HashMap<>();
  static {
    addEn.put("em", "me'n");
    addEn.put("et", "te'n");
    addEn.put("es", "se'n");
    addEn.put("ens", "ens en");
    addEn.put("us", "us en");
    addEn.put("li", "li'n");
    addEn.put("els", "els en");
    addEn.put("se'm", "se me'n");
    addEn.put("se't", "se te'n");
    addEn.put("se li", "se li'n");
    addEn.put("se'ns", "se'ns en");
    addEn.put("se us", "se us en");
    addEn.put("se vos", "se vos en");
    addEn.put("se'ls", "se'ls en");
    addEn.put("hi", "n'hi");
    addEn.put("", "en");
  }

  private static Map<String, String> removeReflexive = new HashMap<>();
  static {
    removeReflexive.put("em", "");
    removeReflexive.put("me", "");
    removeReflexive.put("m'", "");
    removeReflexive.put("et", "");
    removeReflexive.put("te", "");
    removeReflexive.put("t'", "");
    removeReflexive.put("es", "");
    removeReflexive.put("se", "");
    removeReflexive.put("s'", "");
    removeReflexive.put("ens", "");
    removeReflexive.put("us", "");
    removeReflexive.put("vos", "");
    removeReflexive.put("se'm", "em");
    removeReflexive.put("se m'", "m'");
    removeReflexive.put("se't", "et");
    removeReflexive.put("se t'", "t'");
    removeReflexive.put("se li", "li");
    removeReflexive.put("se'ns", "ens");
    removeReflexive.put("se us", "us");
    removeReflexive.put("se'ls", "els");
  }

  private static Map<String, String> addReflexiveVowel = new HashMap<>();
  static {
    addReflexiveVowel.put("1S", "m'");
    addReflexiveVowel.put("2S", "t'");
    addReflexiveVowel.put("3S", "s'");
    addReflexiveVowel.put("1P", "ens ");
    addReflexiveVowel.put("2P", "us ");
    addReflexiveVowel.put("3P", "s'");
  }

  private static Map<String, String> addReflexiveConsonant = new HashMap<>();
  static {
    addReflexiveConsonant.put("1S", "em ");
    addReflexiveConsonant.put("2S", "et ");
    addReflexiveConsonant.put("3S", "es ");
    addReflexiveConsonant.put("1P", "ens ");
    addReflexiveConsonant.put("2P", "us ");
    addReflexiveConsonant.put("3P", "es ");
  }

  private static Map<String, String> addReflexiveImperative = new HashMap<>();
  static {
    addReflexiveImperative.put("2S", "'t");
    addReflexiveImperative.put("3S", "'s");
    addReflexiveImperative.put("1P", "-nos");
    addReflexiveImperative.put("2P", "-vos");
    addReflexiveImperative.put("3P", "-se");
  }

  @Override
  public RuleMatch acceptRuleMatch(RuleMatch match, Map<String, String> arguments, int patternTokenPos,
      AnalyzedTokenReadings[] patternTokens) throws IOException {

    List<String> replacements = new ArrayList<>();
    List<String> actions = Arrays.asList(getRequired("actions", arguments).split(","));
    String newLemma = getOptional("newLemma", arguments);
    int posWord = 0;
    AnalyzedTokenReadings[] tokens = match.getSentence().getTokensWithoutWhitespace();
    while (posWord < tokens.length
        && (tokens[posWord].getStartPos() < match.getFromPos() || tokens[posWord].isSentenceStart())) {
      posWord++;
    }
    int toLeft = 0;
    boolean done = false;
    String firstVerb = "";
    String firstVerbPersonaNumber = "";
    String firstVerbPersonaNumberImperative = "";
    String replacementVerb = "";
    int firstVerbPos = 0;
    boolean inPronouns = false;
    boolean firstVerbValid = false;
    while (!done && posWord - toLeft > 0) {
      AnalyzedTokenReadings currentTkn = tokens[posWord - toLeft];
      String currentTknStr = currentTkn.getToken();
      // change lemma if asked
      if (toLeft == 0 && newLemma != null) {
        List<String> postags = new ArrayList<>();
        for (AnalyzedToken reading : currentTkn) {
          if (reading.getPOSTag() != null && reading.getPOSTag().startsWith("V")) {
            postags.add(reading.getPOSTag());
          }
        }
        String targetPostag = synth.getTargetPosTag(postags, "");
        if (!targetPostag.isEmpty()) {
          AnalyzedToken at = new AnalyzedToken(currentTknStr, targetPostag, newLemma);
          String[] synthForms = synth.synthesize(at, targetPostag);
          if (synthForms != null && synthForms.length > 0) {
            replacementVerb = synthForms[0];
          }
        }
      }
      boolean isVerb = currentTkn.hasPosTagStartingWith("V");
      boolean isPronoun = currentTkn.matchesPosTagRegex("P0.{6}|PP3CN000|PP3NN000|PP3..A00|PP3CP000|PP3CSD00");
      if (isPronoun) {
        inPronouns = true;
      }
      if (isPronoun || (isVerb && !inPronouns && !firstVerbValid) || currentTknStr.equalsIgnoreCase("de")
          || currentTknStr.equalsIgnoreCase("d'")) {
        if (isVerb) {
          firstVerb = currentTknStr;
          firstVerbPos = toLeft;
          firstVerbValid = currentTkn.matchesPosTagRegex("V.[SI].*");
          if (firstVerbValid) {
            firstVerbPersonaNumber = currentTkn.readingWithTagRegex("V.[SI].*").getPOSTag().substring(4, 6);
          }
          if (currentTkn.matchesPosTagRegex("V.M.*")) {
            firstVerbPersonaNumberImperative = currentTkn.readingWithTagRegex("V.M.*").getPOSTag().substring(4, 6);
          }
        }
        toLeft++;
      } else {
        done = true;
        if (toLeft > 0) {
          toLeft--;
        }
      }
    }
    if (!firstVerbValid) {
      return null;
    }
    StringBuilder sb = new StringBuilder();
    for (int i = posWord - toLeft; i < posWord - firstVerbPos; i++) {
      sb.append(tokens[i].getToken());
      if (tokens[i + 1].isWhitespaceBefore()) {
        sb.append(" ");
      }
    }
    String pronounsStr = sb.toString().trim();
    sb = new StringBuilder();
    for (int i = posWord - firstVerbPos; i <= posWord; i++) {
      if (i == posWord && !replacementVerb.isEmpty()) {
        sb.append(replacementVerb);
      } else {
        sb.append(tokens[i].getToken());
      }
      if (i + 1 < tokens.length && tokens[i + 1].isWhitespaceBefore()) {
        sb.append(" ");
      }
    }
    String verbStr = sb.toString().trim();
    for (String action : actions) {
      String replacement = "";
      switch (action) {
      case "addPronounEn":
        replacement = doAddPronounEn(firstVerb, pronounsStr, verbStr);
        break;
      case "removePronounReflexive":
        replacement = doRemovePronounReflexive(firstVerb, pronounsStr, verbStr);
        break;
      case "replaceEmEn":
        replacement = doReplaceEmEn(firstVerb, pronounsStr, verbStr);
        break;
      case "addPronounReflexive":
        replacement = doAddPronounReflexive(firstVerb, pronounsStr, verbStr, firstVerbPersonaNumber);
        break;
      case "addPronounReflexiveImperative":
        replacement = doAddPronounReflexiveImperative(firstVerb, pronounsStr, verbStr,
            firstVerbPersonaNumberImperative);
        break;
      }
      if (!replacement.isEmpty()) {
        replacements.add(replacement);
      }
    }
    if (replacements.isEmpty()) {
      return null;
    }
    RuleMatch ruleMatch = new RuleMatch(match.getRule(), match.getSentence(), tokens[posWord - toLeft].getStartPos(),
        match.getToPos(), match.getMessage(), match.getShortMessage());
    ruleMatch.setType(match.getType());
    ruleMatch.setSuggestedReplacements(replacements);
    return ruleMatch;
  }

  private String doAddPronounEn(String firstVerb, String pronounsStr, String verbStr) {
    Map<String, String> transform;
    String replacement = "";
    String between = "";
    if (pApostropheNeeded.matcher(firstVerb).matches()) {
      transform = addEnApostrophe;
    } else {
      transform = addEn;
      between = " ";
    }
    String pronounsReplacement = transform.get(pronounsStr.toLowerCase());
    if (pronounsReplacement != null) {
      replacement = StringTools.preserveCase(pronounsReplacement, (pronounsStr + between + verbStr).trim()) + between
          + verbStr.toLowerCase();
    }
    return replacement;
  }

  private String doRemovePronounReflexive(String firstVerb, String pronounsStr, String verbStr) {
    String replacement = "";
    String between = " ";
    String pronounsReplacement = removeReflexive.get(pronounsStr.toLowerCase());
    if (pronounsReplacement != null) {
      replacement = StringTools.preserveCase(pronounsReplacement + between + verbStr, pronounsStr).trim()
          .replaceAll("' ", "'");
    }
    return replacement;
  }

  private String doAddPronounReflexive(String firstVerb, String pronounsStr, String verbStr,
      String firstVerbPersonaNumber) {
    String pronounToAdd = "";
    String replacement = "";
    if (pronounsStr.isEmpty()) {
      if (pApostropheNeeded.matcher(verbStr).matches()) {
        pronounToAdd = addReflexiveVowel.get(firstVerbPersonaNumber);
      } else {
        pronounToAdd = addReflexiveConsonant.get(firstVerbPersonaNumber);
      }
      if (pronounToAdd != null) {
        replacement = StringTools.preserveCase(pronounToAdd + verbStr, verbStr).trim().replaceAll("' ", "'");
      }
    }
    return replacement;
  }

  private String doAddPronounReflexiveImperative(String firstVerb, String pronounsStr, String verbStr,
      String firstVerbPersonaNumber) {
    String pronounToAdd = "";
    String replacement = "";
    if (pronounsStr.isEmpty()) {
      pronounToAdd = addReflexiveImperative.get(firstVerbPersonaNumber);
      if (pronounToAdd != null) {
        replacement = StringTools.preserveCase(verbStr + pronounToAdd, verbStr).trim();
      }
    }
    return replacement;
  }

  private String doReplaceEmEn(String firstVerb, String pronounsStr, String verbStr) {
    String replacement = "";
    if (pronounsStr.equalsIgnoreCase("em")) {
      replacement = StringTools.preserveCase("en", pronounsStr) + " " + verbStr;
    }
    if (pronounsStr.equalsIgnoreCase("m'")) {
      replacement = StringTools.preserveCase("n'", pronounsStr) + verbStr;
    }
    if (pronounsStr.equalsIgnoreCase("m'hi")) {
      replacement = StringTools.preserveCase("n'hi ", pronounsStr) + verbStr;
    }
    return replacement;
  }

}
