/* LanguageTool, a natural language style checker 
 * Copyright (C) 2020 Jaume Ortolà (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.fr;

import org.languagetool.AnalyzedToken;
import org.languagetool.AnalyzedTokenReadings;
import org.languagetool.JLanguageTool;
import org.languagetool.Language;
import org.languagetool.language.French;
import org.languagetool.rules.Rule;
import org.languagetool.rules.RuleMatch;
import org.languagetool.rules.patterns.RuleFilter;
import org.languagetool.synthesis.FrenchSynthesizer;
import org.languagetool.tools.StringTools;

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

/*
 * Create suggestions for: determiner + noun/adjective
 */
public class WordWithDeterminerFilter extends RuleFilter {

  private static final String determinerRegexp = "(P.)?D .*|J .*|V.* ppa .*";
  private static final Pattern DETERMINER = Pattern.compile(determinerRegexp);
  private static final String wordRegexp = "[ZNJ] .*|V.* ppa .*";
  private static final Pattern WORD = Pattern.compile(wordRegexp);

  // 0=MS, 1=FS, 2=MP, 3=FP
  private static final String[] GenderNumber = { "([me]) (s|sp)", "([fe]) (s|sp)", "([me]) (p|sp)", "([fe]) (p|sp)" };
  private static final String determiner = "((P.)?D |J |V.* ppa )";

  private static final List<String> exceptionsDeterminer =
    Arrays.asList("bels", "fols", "mols", "nouvels");

  @Override
  public RuleMatch acceptRuleMatch(RuleMatch match, Map<String, String> arguments, int patternTokenPos,
      AnalyzedTokenReadings[] patternTokens) throws IOException {  
    
//    if (match.getSentence().getText().contains("plein Londres")) {
//      int ii=0;
//      ii++;
//    }
    
    Language lang = new French();
    JLanguageTool lt = lang.createDefaultJLanguageTool(); 
    
    String wordFrom = getRequired("wordFrom", arguments);
    String determinerFrom = getRequired("determinerFrom", arguments);
    int posWord = 0;
    int posDeterminer = 0;
    if (wordFrom != null && determinerFrom != null) {
      posWord = Integer.parseInt(wordFrom);
      if (posWord < 1 || posWord > patternTokens.length) {
        throw new IllegalArgumentException("WordWithDeterminerFilter: Index out of bounds in "
            + match.getRule().getFullId() + ", wordFrom: " + posWord);
      }
      posDeterminer = Integer.parseInt(determinerFrom);
      if (posDeterminer < 1 || posDeterminer > patternTokens.length) {
        throw new IllegalArgumentException("WordWithDeterminerFilter: Index out of bounds in "
            + match.getRule().getFullId() + ", posDeterminer: " + posWord);
      }
    } else {
      throw new IllegalArgumentException("WordWithDeterminerFilter: undefined parameters wordFrom or determinerFrom in "
          + match.getRule().getFullId());
    }
    AnalyzedTokenReadings atrDeterminer = patternTokens[posDeterminer - 1];
    AnalyzedTokenReadings atrWord = patternTokens[posWord - 1];
    boolean isDeterminerCapitalized = StringTools.isCapitalizedWord(atrDeterminer.getToken());
    boolean isWordCapitalized = StringTools.isCapitalizedWord(atrWord.getToken());
    boolean isDeterminerAllupper = StringTools.isAllUppercase(atrDeterminer.getToken())
        && !atrDeterminer.getToken().equalsIgnoreCase("L'");
    boolean isWordAllupper = StringTools.isAllUppercase(atrWord.getToken());
    AnalyzedToken atDeterminer = getAnalyzedToken(atrDeterminer, DETERMINER);
    AnalyzedToken atWord = getAnalyzedToken(atrWord, WORD);
    if (atWord == null || atDeterminer == null) {
      throw new RuntimeException(
          "Error analyzing sentence: '" + match.getSentence().getText() + "' with rule " + match.getRule().getFullId());
    }
    boolean isNoun = atWord.getPOSTag().startsWith("N") || atWord.getPOSTag().startsWith("Z");
    boolean isAdjective = atWord.getPOSTag().startsWith("J");
    // boolean isParticiple = atWord.getPOSTag().startsWith("V");

    String prefix = "[ZNJ] ";
    if (isNoun && !isAdjective) {
      prefix = "[NZ] ";
    } else if (!isNoun && isAdjective) {
      prefix = "J ";
    }

    // synthesize all forms
    String[][] determinerForms = new String[4][];
    String[][] wordForms = new String[4][];
    for (int i = 0; i < 4; i++) {
      determinerForms[i] = FrenchSynthesizer.INSTANCE.synthesize(atDeterminer, determiner + GenderNumber[i], true);
      wordForms[i] = FrenchSynthesizer.INSTANCE.synthesize(atWord, prefix + GenderNumber[i], true);
      // if it cannot be synthesyzed, keep the original determiner
      if (determinerForms[i].length == 0 && atDeterminer.getPOSTag().matches(".+" + GenderNumber[i])) {
        determinerForms[i] = new String[] { atDeterminer.getToken() };
      }
      // if it cannot be synthesyzed, keep the original word
      if (wordForms[i].length == 0 && atWord.getPOSTag().matches(".+" + GenderNumber[i])) {
        wordForms[i] = new String[] { atWord.getToken() };
      }
    }

    //FIXME: enabling and disabling rules is not a good solution 
    // if several filters use the same JLanguageTool instance 
    for (Rule r : lt.getAllRules()) {
      if (r.getCategory().getId().toString().equals("CAT_ELISION") || r.getId().equals("CET_CE")
          || r.getId().equals("CE_CET") || r.getId().equals("MA_VOYELLE") || r.getId().equals("MON_NFS")
          || r.getId().equals("VIEUX")) {
        lt.enableRule(r.getId());
      } else {
        lt.disableRule(r.getId());
      }
    }

    // generate suggestions
    List<String> replacements = new ArrayList<>();
    for (int i = 0; i < 4; i++) {
      for (int cw = 0; cw < wordForms[i].length; cw++) {
        for (int cd = 0; cd < determinerForms[i].length; cd++) {
          if (exceptionsDeterminer.contains(determinerForms[i][cd])) {
            continue;
          }
          if (determinerForms[i][cd] != null && wordForms[i][cw] != null) {
            String determiner = determinerForms[i][cd];
            String word = wordForms[i][cw];
            if (isDeterminerCapitalized) {
              determiner = StringTools.uppercaseFirstChar(determiner);
            }
            if (isWordCapitalized) {
              word = StringTools.uppercaseFirstChar(word);
            }
            if (isDeterminerAllupper) {
              determiner = determiner.toUpperCase();
            }
            if (isWordAllupper) {
              word = word.toUpperCase();
            }
            String r = determiner + " " + word;
            r = r.replace("' ", "'");
            // remove suggestions with errors
            List<RuleMatch> matches = lt.check(r);
            if (matches.size() == 0 && !replacements.contains(r)) {
              if (r.endsWith(atWord.getToken())) {
                replacements.add(0, r);
              } else {
                replacements.add(r);
              }
            }
          }
        }
      }
    }

    String message = match.getMessage();
    RuleMatch ruleMatch = new RuleMatch(match.getRule(), match.getSentence(), match.getFromPos(), match.getToPos(),
        message, match.getShortMessage());
    ruleMatch.setType(match.getType());
    //add existing suggestion in the XML rule
    replacements.addAll(0, match.getSuggestedReplacements());
    if (!replacements.isEmpty()) {
      ruleMatch.setSuggestedReplacements(replacements);
    }
    return ruleMatch;
  }

  private AnalyzedToken getAnalyzedToken(AnalyzedTokenReadings aToken, Pattern pattern) {
    for (AnalyzedToken analyzedToken : aToken) {
      String posTag = analyzedToken.getPOSTag();
      if (posTag == null) {
        posTag = "UNKNOWN";
      }
      final Matcher m = pattern.matcher(posTag);
      if (m.matches()) {
        return analyzedToken;
      }
    }
    return null;
  }

}
