/**
   * Analyze a page to check if errors are present.
   *
   * @param analysis Page analysis.
   * @param errors Errors found in the page.
   * @param onlyAutomatic True if analysis could be restricted to errors automatically fixed.
   * @return Flag indicating if the error was found.
   */
  @Override
  public boolean analyze(
      PageAnalysis analysis, Collection<CheckErrorResult> errors, boolean onlyAutomatic) {
    if ((analysis == null) || (analysis.getPage() == null)) {
      return false;
    }

    // Analyze each title
    String contents = analysis.getContents();
    boolean result = false;
    for (PageElementTitle title : analysis.getTitles()) {

      // Check if there's something in the title
      boolean textFound = false;
      int currentIndex = title.getBeginIndex();
      int lastIndex = title.getEndIndex();
      while ((currentIndex < lastIndex) && (contents.charAt(currentIndex) == '=')) {
        currentIndex++;
      }
      while (!textFound && (currentIndex < lastIndex) && (contents.charAt(currentIndex) != '=')) {
        currentIndex = getFirstIndexAfterSpace(contents, currentIndex);
        if (currentIndex < lastIndex) {
          PageElementComment comment = null;
          PageElementTag tag = null;
          char currentChar = contents.charAt(currentIndex);
          if (currentChar == '<') {
            comment = analysis.isInComment(currentIndex);
            tag = analysis.isInTag(currentIndex, PageElementTag.TAG_WIKI_NOWIKI);
          }
          if (comment != null) {
            currentIndex = comment.getEndIndex();
          } else if (tag != null) {
            currentIndex = tag.getCompleteEndIndex();
          } else if (currentChar != '=') {
            if (!Character.isWhitespace(currentChar)) {
              textFound = true;
            }
            currentIndex++;
          }
        }
      }

      // Report error
      if (!textFound) {
        if (errors == null) {
          return true;
        }
        result = true;
        if ((lastIndex < contents.length()) && (contents.charAt(lastIndex) == '\n')) {
          lastIndex++;
        }
        CheckErrorResult errorResult =
            createCheckErrorResult(analysis, title.getBeginIndex(), lastIndex);
        errorResult.addReplacement("");
        errors.add(errorResult);
      }
    }

    return result;
  }
  /**
   * Analyze a page to check if errors are present.
   *
   * @param analysis Page analysis.
   * @param errors Errors found in the page.
   * @param onlyAutomatic True if analysis could be restricted to errors automatically fixed.
   * @return Flag indicating if the error was found.
   */
  @Override
  public boolean analyze(
      PageAnalysis analysis, Collection<CheckErrorResult> errors, boolean onlyAutomatic) {
    if (analysis == null) {
      return false;
    }

    // Analyze each <small> tag
    boolean result = false;
    List<PageElementTag> smallTags = analysis.getTags(PageElementTag.TAG_HTML_SMALL);
    for (PageElementTag smallTag : smallTags) {
      int index = smallTag.getBeginIndex();
      PageElementTag refTag = analysis.getSurroundingTag(PageElementTag.TAG_WIKI_REF, index);
      PageElementTag subTag = analysis.getSurroundingTag(PageElementTag.TAG_HTML_SUB, index);
      PageElementTag supTag = analysis.getSurroundingTag(PageElementTag.TAG_HTML_SUP, index);
      if ((refTag != null) || (subTag != null) || (supTag != null)) {
        if (errors == null) {
          return true;
        }
        result = true;
        CheckErrorResult errorResult =
            createCheckErrorResult(analysis, smallTag.getBeginIndex(), smallTag.getEndIndex());
        errors.add(errorResult);
      }
    }

    return result;
  }
  /**
   * Analyze a page to check if errors are present.
   * 
   * @param analysis Page analysis.
   * @param errors Errors found in the page.
   * @param onlyAutomatic True if analysis could be restricted to errors automatically fixed.
   * @return Flag indicating if the error was found.
   */
  @Override
  public boolean analyze(
      PageAnalysis analysis,
      Collection<CheckErrorResult> errors, boolean onlyAutomatic) {
    if ((analysis == null) || (analysis.getPage() == null)) {
      return false;
    }

    // Analyze each PMID
    boolean result = false;
    List<PageElementPMID> pmids = analysis.getPMIDs();
    if ((pmids == null) || (pmids.isEmpty())) {
      return false;
    }
    for (PageElementPMID pmid : pmids) {
      if (!pmid.isTemplateParameter() && pmid.isCorrect()) {
        if (errors == null) {
          return true;
        }
        result = true;
        CheckErrorResult errorResult = createCheckErrorResult(
            analysis, pmid.getBeginIndex(), pmid.getEndIndex());
        List<String[]> pmidTemplates = analysis.getWPCConfiguration().getStringArrayList(
            WPCConfigurationStringList.PMID_TEMPLATES);
        if (pmidTemplates != null) {
          for (String[] pmidTemplate : pmidTemplates) {
            if (pmidTemplate.length > 2) {
              String templateName = pmidTemplate[0];
              String[] params = pmidTemplate[1].split(",");
              Boolean suggested = Boolean.valueOf(pmidTemplate[2]);
              if ((params.length > 0) && (Boolean.TRUE.equals(suggested))) {
                StringBuilder replacement = new StringBuilder();
                replacement.append("{{");
                replacement.append(templateName);
                replacement.append("|");
                if (!"1".equals(params[0])) {
                  replacement.append(params[0]);
                  replacement.append("=");
                }
                replacement.append(pmid.getPMID());
                replacement.append("}}");
                errorResult.addReplacement(replacement.toString());
              }
            }
          }
        }
        errors.add(errorResult);
      }
    }

    return result;
  }
  /**
   * Analyze a page to check if errors are present.
   *
   * @param analysis Page analysis.
   * @param errors Errors found in the page.
   * @param onlyAutomatic True if analysis could be restricted to errors automatically fixed.
   * @return Flag indicating if the error was found.
   */
  @Override
  public boolean analyze(
      PageAnalysis analysis, Collection<CheckErrorResult> errors, boolean onlyAutomatic) {
    if (analysis == null) {
      return false;
    }

    // Check level of each title
    List<PageElementTitle> titles = analysis.getTitles();
    if ((titles == null) || (titles.size() == 0)) {
      return false;
    }
    for (PageElementTitle title : titles) {
      if (title.getLevel() < 3) {
        return false;
      }
    }

    if (errors == null) {
      return true;
    }
    CheckErrorResult errorResult =
        createCheckErrorResult(
            analysis, titles.get(0).getBeginIndex(), titles.get(0).getEndIndex());
    if (titles.size() == 1) {
      errorResult.addReplacement(
          PageElementTitle.createTitle(2, titles.get(0).getTitle(), titles.get(0).getAfterTitle()));
    }
    errorResult.addEditTocAction(titles.get(0));
    errors.add(errorResult);
    return true;
  }
  /**
   * Bot fixing of all the errors in the page.
   *
   * @param analysis Page analysis.
   * @return Page contents after fix.
   */
  @Override
  protected String internalBotFix(PageAnalysis analysis) {
    String contents = analysis.getContents();
    if (!analysis.areTitlesReliable()) {
      return contents;
    }

    // Compute minimum title level
    List<PageElementTitle> titles = analysis.getTitles();
    if ((titles == null) || (titles.size() == 0)) {
      return contents;
    }
    int minTitle = Integer.MAX_VALUE;
    for (PageElementTitle title : titles) {
      if (title.getLevel() < minTitle) {
        minTitle = title.getLevel();
      }
    }
    if (minTitle < 3) {
      return contents;
    }

    // Replace titles
    StringBuilder tmp = new StringBuilder();
    int lastIndex = 0;
    int offset = minTitle - 2;
    for (PageElementTitle title : titles) {
      if (lastIndex < title.getBeginIndex()) {
        tmp.append(contents.substring(lastIndex, title.getBeginIndex()));
        lastIndex = title.getBeginIndex();
      }
      tmp.append(
          PageElementTitle.createTitle(
              title.getLevel() - offset, title.getTitle(), title.getAfterTitle()));
      if (title.getAfterTitle() != null) {
        tmp.append(title.getAfterTitle());
      }
      lastIndex = title.getEndIndex();
    }
    if (lastIndex < contents.length()) {
      tmp.append(contents.substring(lastIndex));
    }

    return tmp.toString();
  }
  /**
   * Create a default popup menu.
   * 
   * @param textPane Text pane.
   * @param position Position in the text.
   * @param pageAnalysis Page analysis.
   * @return Popup menu.
   */
  protected JPopupMenu createDefaultPopup(
      MWPane textPane, int position,
      PageAnalysis pageAnalysis) {

    // Basic checks
    if (pageAnalysis == null) {
      return null;
    }

    // Find where the user has clicked
    PageElement element = pageAnalysis.isInElement(position);

    // Comment
    if (element instanceof PageElementComment) {
      return null;
    }

    // Menu for internal link
    if (element instanceof PageElementInternalLink) {
      PageElementInternalLink internalLink = (PageElementInternalLink) element;
      return createDefaultPopupInternalLink(textPane, position, pageAnalysis, internalLink);
    }

    // Menu for image
    if (element instanceof PageElementImage) {
      PageElementImage image = (PageElementImage) element;
      return createDefaultPopupImage(textPane, position, pageAnalysis, image);
    }

    // Menu for external link
    if (element instanceof PageElementExternalLink) {
      PageElementExternalLink externalLink = (PageElementExternalLink) element;
      return createDefaultPopupExternalLink(pageAnalysis, position, externalLink);
    }

    // Menu for template
    if (element instanceof PageElementTemplate) {
      PageElementTemplate template = (PageElementTemplate) element;
      return createDefaultPopupTemplate(pageAnalysis, position, template);
    }

    // Menu for category
    if (element instanceof PageElementCategory) {
      PageElementCategory category = (PageElementCategory) element;
      return createDefaultPopupCategory(textPane, position, pageAnalysis, category);
    }

    // Menu for interwiki
    if (element instanceof PageElementInterwikiLink) {
      PageElementInterwikiLink interwiki = (PageElementInterwikiLink) element;
      return createDefaultPopupInterwikiLink(textPane, position, pageAnalysis, interwiki);
    }

    // Menu for language
    if (element instanceof PageElementLanguageLink) {
      PageElementLanguageLink language = (PageElementLanguageLink) element;
      return createDefaultPopupLanguageLink(textPane, position, pageAnalysis, language);
    }

    // Menu for parameter
    if (element instanceof PageElementParameter) {
      PageElementParameter parameter = (PageElementParameter) element;
      return createDefaultPopupParameter(pageAnalysis, position, parameter);
    }

    // Menu for function
    if (element instanceof PageElementFunction) {
      PageElementFunction function = (PageElementFunction) element;
      return createDefaultPopupFunction(pageAnalysis, position, function);
    }

    // Menu for ISBN
    if (element instanceof PageElementISBN) {
      PageElementISBN isbn = (PageElementISBN) element;
      return createDefaultPopupISBN(pageAnalysis, position, isbn);
    }

    // Menu for ISSN
    if (element instanceof PageElementISSN) {
      PageElementISSN issn = (PageElementISSN) element;
      return createDefaultPopupISSN(pageAnalysis, position, issn);
    }

    // Default menu
    BasicMenuCreator menu = new BasicMenuCreator();
    JPopupMenu popup = menu.createPopupMenu(GT._(
        "Page: {0}",
        limitTextLength(pageAnalysis.getPage().getTitle(), 50)));
    menu.addCurrentChapter(popup, position, pageAnalysis);
    menu.addSeparator(popup);
    menu.addView(wikipedia, popup, pageAnalysis.getPage(), false);
    return popup;
  }
  /**
   * Analyze a page to check if errors are present.
   * 
   * @param analysis Page analysis.
   * @param errors Errors found in the page.
   * @param onlyAutomatic True if analysis could be restricted to errors automatically fixed.
   * @return Flag indicating if the error was found.
   */
  @Override
  public boolean analyze(
      PageAnalysis analysis,
      Collection<CheckErrorResult> errors,
      boolean onlyAutomatic) {
    if (analysis == null) {
      return false;
    }

    // Retrieve possible templates to replace the link to other language
    List<String> templatesList = getTemplatesList();

    // Analyzing the text from the beginning
    boolean result = false;
    EnumWikipedia toWiki = analysis.getWikipedia();
    for (PageElementInterwikiLink link : analysis.getInterwikiLinks()) {
      if (isLanguageLink(link, toWiki)) {
        if (errors == null) {
          return true;
        }
        result = true;
        CheckErrorResult errorResult = createCheckErrorResult(
            analysis, link.getBeginIndex(), link.getEndIndex());
        String lgCode = link.getInterwiki().getPrefix();
        EnumWikipedia fromWiki = EnumWikipedia.getWikipedia(lgCode);
        if ((fromWiki != null) && (fromWiki.getSettings().getCode().equals(lgCode))) {
          String pageTitle = link.getLink();
          errorResult.addPossibleAction(
              GT._("Check language links"),
              new CheckLanguageLinkActionProvider(
                  fromWiki, toWiki,
                  pageTitle, link.getText()));
          if ((templatesList != null) && (templatesList.size() > 0)) {
            for (String template : templatesList) {
              String[] templateArgs = template.split("\\|");
              if (templateArgs.length >= 5) {

                // Compute order of parameters
                List<String> order = new ArrayList<>();
                if (templateArgs.length >= 6) {
                  String[] tmpOrder = templateArgs[5].split(",");
                  for (String tmp : tmpOrder) {
                    order.add(tmp);
                  }
                }
                for (int numParam = 1; numParam <= 4; numParam++) {
                  if (!order.contains(templateArgs[numParam])) {
                    order.add(templateArgs[numParam]);
                  }
                }

                StringBuilder prefix = new StringBuilder();
                StringBuilder suffix = new StringBuilder();
                prefix.append("{{");
                prefix.append(templateArgs[0]);
                boolean localTitle = false;
                for (String param : order) {
                  if (param.equals(templateArgs[1])) {
                    prefix.append("|");
                    prefix.append(templateArgs[1]);
                    prefix.append("=");
                    localTitle = true;
                  } else {
                    String value = "";
                    if (param.equals(templateArgs[2])) {
                      value = lgCode;
                    } else if (param.equals(templateArgs[3])) {
                      value = pageTitle;
                    } else if (param.equals(templateArgs[4])) {
                      value = (link.getText() != null) ? link.getText() : pageTitle;
                    }
                    StringBuilder buffer = localTitle ? suffix : prefix;
                    buffer.append("|");
                    buffer.append(param);
                    buffer.append("=");
                    if (value != null) {
                      buffer.append(value);
                    }
                  }
                }
                suffix.append("}}");
                String question = GT._("What is the title of the page on this wiki ?");
                AddTextActionProvider action = null;
                if ((link.getText() != null) && (!link.getText().equals(pageTitle))) {
                  String[] possibleValues = { null, pageTitle, link.getText() };
                  action = new AddTextActionProvider(
                      prefix.toString(), suffix.toString(), null, question,
                      possibleValues, false, null, checker);
                } else {
                  action = new AddTextActionProvider(
                      prefix.toString(), suffix.toString(), null, question,
                      pageTitle, checker);
                }
                errorResult.addPossibleAction(
                    GT._("Replace using template {0}", "{{" + templateArgs[0] + "}}"),
                    action);
              }
            }
          }
          errorResult.addPossibleAction(
              GT._("External Viewer"),
              new BasicActionProvider(
                  new ActionExternalViewer(fromWiki, pageTitle)));
        }
        errors.add(errorResult);
      }
    }

    return result;
  }
  /**
   * Fix all the errors in the page.
   * 
   * @param fixName Fix name (extracted from getGlobalFixes()).
   * @param analysis Page analysis.
   * @param textPane Text pane.
   * @return Page contents after fix.
   */
  @Override
  public String fix(String fixName, PageAnalysis analysis, MWPane textPane) {

    // Initialize
    API api = APIFactory.getAPI();
    StringBuilder tmpContents = new StringBuilder();
    int currentIndex = 0;

    // Manage templates that can be used to replace a link to an other language
    List<String> templatesList = getTemplatesList();
    String[] templateArgs = null;
    if ((templatesList != null) && (templatesList.size() > 0)) {
      String[] tmp = templatesList.get(0).split("\\|");
      if (tmp.length >= 5) {
        templateArgs = tmp;
      }
    }

    // Check all internal links
    Object highlight = null;
    String contents = analysis.getContents();
    try {
      EnumWikipedia toWiki = analysis.getWikipedia();
      for (PageElementInterwikiLink link : analysis.getInterwikiLinks()) {
        if (isLanguageLink(link, toWiki)) {
          String lgCode = link.getInterwiki().getPrefix();
          EnumWikipedia fromWiki = EnumWikipedia.getWikipedia(lgCode);
          if ((fromWiki != null) && (fromWiki.getSettings().getCode().equals(lgCode))) {
            String pageTitle = link.getLink();
            int beginIndex = link.getBeginIndex();
            int endIndex = link.getEndIndex();
            String replacement = null;

            // Display selection
            highlight = addHighlight(textPane, beginIndex, endIndex);
            textPane.select(beginIndex, endIndex);

            // Check for language link
            String toTitle = api.getLanguageLink(fromWiki, toWiki, pageTitle);
            if (toTitle != null) {

              // List possible replacements
              List<String> possibleValues = new ArrayList<String>();
              String possible = null;
              possible = PageElementInternalLink.createInternalLink(toTitle, link.getText());
              if (!possibleValues.contains(possible)) {
                possibleValues.add(possible);
              }
              possible = PageElementInternalLink.createInternalLink(toTitle, link.getFullLink());
              if (!possibleValues.contains(possible)) {
                possibleValues.add(possible);
              }
              possible = PageElementInternalLink.createInternalLink(toTitle, null);
              if (!possibleValues.contains(possible)) {
                possibleValues.add(possible);
              }
              possibleValues.add(GT._("Do not replace"));
              possibleValues.add(GT._("Cancel"));

              // Ask user what replacement to use
              String message = GT._(
                  "The page \"{0}\" in \"{1}\" has a language link to \"{2}\": {3}.\n" +
                  "With what text do you want to replace the link ?",
                  new Object[] { pageTitle, fromWiki, toWiki, toTitle } );
              int answer = Utilities.displayQuestion(
                  textPane.getParent(), message,
                  possibleValues.toArray());
              if ((answer < 0) || (answer >= possibleValues.size() - 1)) {
                break;
              } else if (answer < possibleValues.size() - 2) {
                replacement = possibleValues.get(answer);
              }
            } else if (templateArgs != null) {
              String message =
                  GT._("The page \"{0}\" in \"{1}\" doesn''t have a language link to \"{2}\".",
                       new Object[] { pageTitle, fromWiki, toWiki }) +"\n" +
                  GT._("You can replace the link using template {0}.",
                       "{{" + templateArgs[0] + "}}") + "\n" +
                  GT._("What is the title of the page on this wiki ?");
              if ((link.getText() != null) && (!link.getText().equals(pageTitle))) {
                toTitle = Utilities.askForValue(
                    textPane.getParent(), message, link.getText(), checker);
              } else {
                toTitle = Utilities.askForValue(
                    textPane.getParent(), message, pageTitle, checker);
              }
              if (toTitle != null) {
                replacement =
                    "{{" + templateArgs[0] +
                    "|" + templateArgs[1] + "=" + toTitle +
                    "|" + templateArgs[2] + "=" + lgCode +
                    "|" + templateArgs[3] + "=" + pageTitle +
                    "|" + templateArgs[4] + "=" + ((link.getText() != null) ? link.getText() : pageTitle) +
                    "}}";
              }
            }

            // Do the replacement
            if (replacement != null) {
              if (currentIndex < link.getBeginIndex()) {
                tmpContents.append(contents.substring(currentIndex, link.getBeginIndex()));
              }
              tmpContents.append(replacement);
              currentIndex = link.getEndIndex();
            }
            removeHighlight(textPane, highlight);
            highlight = null;
          }
        }
      }
    } catch (APIException e) {
      //
    }
    removeHighlight(textPane, highlight);
    highlight = null;

    // Return result
    if (currentIndex == 0) {
      return contents;
    }
    if (currentIndex < contents.length()) {
      tmpContents.append(contents.substring(currentIndex));
    }
    return tmpContents.toString();
  }