/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package opennlp.tools.cmdline;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import opennlp.tools.cmdline.ArgumentParser.Argument;

public class GenerateManualTool {

  private static final Logger logger = LoggerFactory.getLogger(GenerateManualTool.class);

  private static final int MAX_LINE_LENGTH = 110; // optimized for printing

  public static void main(String[] args) throws FileNotFoundException {

    if (args.length != 1) {
      logger.info(getUsage());
      System.exit(0);
    }

    StringBuilder sb = new StringBuilder();

    appendHeader(sb);

    // organize by package name
    LinkedHashMap<String, Map<String, CmdLineTool>> packageNameToolMap = new LinkedHashMap<>();
    for (Entry<String, CmdLineTool> entry : CLI.getToolLookupMap().entrySet()) {
      final String toolName = entry.getKey();
      final CmdLineTool tool = entry.getValue();
      String packageName = tool.getClass().getPackage().getName();
      packageName = packageName.substring(packageName.lastIndexOf(".") + 1);

      if (!packageNameToolMap.containsKey(packageName)) {
        packageNameToolMap.put(packageName,
            new LinkedHashMap<>());
      }
      packageNameToolMap.get(packageName).put(toolName, tool);
    }

    // add tools grouped by package
    for (Entry<String, Map<String, CmdLineTool>> entry : packageNameToolMap.entrySet()) {
      appendToolGroup(entry.getKey(), entry.getValue(), sb);
    }

    // footer
    appendFooter(sb);

    // output to file
    try (PrintWriter out = new PrintWriter(args[0])) {
      out.println(sb);
    }
  }

  /**
   * @return this tool usage
   */
  private static String getUsage() {
    return "Requires one argument: \n" +
            "  Path to the output XML file \n";
  }

  /**
   * Appends a group of tools, based on the tool package name
   *
   * @param groupName The groupName to use.
   * @param toolsMap  The {@link Map} of all cmd-line tools available.
   * @param sb The {@link StringBuilder} to fill.
   */
  private static void appendToolGroup(String groupName,
      Map<String, CmdLineTool> toolsMap, StringBuilder sb) {
    sb.append("<section id='tools.cli.").append(groupName).append("'>\n\n");
    sb.append("<title>").append(firstCaps(groupName)).append("</title>\n\n");

    for (Entry<String, CmdLineTool> entry : toolsMap.entrySet()) {
      appendTool(groupName, entry.getKey(), entry.getValue(), sb);
    }

    sb.append("</section>\n\n");

  }

  /**
   * Appends a tool.
   *
   * @param groupName The groupName to use.
   * @param toolName  The name of the tool to use.
   * @param tool  The {@link CmdLineTool} to append.
   * @param sb The {@link StringBuilder} to fill.
   */
  private static void appendTool(String groupName, String toolName,
      CmdLineTool tool, StringBuilder sb) {
    sb.append("<section id='tools.cli.").append(groupName).append(".")
        .append(toolName).append("'>\n\n");
    sb.append("<title>").append(toolName).append("</title>\n\n");
    sb.append("<para>").append(firstCaps(tool.getShortDescription()))
        .append("</para>\n\n");

    appendCode(tool.getHelp(), sb);
    if (TypedCmdLineTool.class.isAssignableFrom(tool.getClass())) {
      appendHelpForTool((TypedCmdLineTool<?,?>) tool, sb);
    }

    sb.append("</section>\n\n");
  }

  private static void appendHelpForTool(TypedCmdLineTool<?,?> tool,
      StringBuilder sb) {
    Class<?> type = tool.type;

    Set<String> formats = StreamFactoryRegistry.getFactories(type).keySet();
    sb.append("<para>The supported formats and arguments are:</para>\n\n");
    Map<String, List<Argument>> formatArguments = new LinkedHashMap<>();
    for (String formatName : formats) {
      if (!StreamFactoryRegistry.DEFAULT_FORMAT.equals(formatName)) {
        ObjectStreamFactory<?,?> format = tool.getStreamFactory(formatName);
        formatArguments.put(formatName,
            ArgumentParser.createArguments(format.getParameters()));

      }
    }
    appendArgumentTable(formatArguments, sb);
  }

  private static void appendArgumentTable(
      Map<String, List<Argument>> formatArguments, StringBuilder sb) {
    sb.append(
        "<informaltable frame='all'><tgroup cols='4' align='left' colsep='1' rowsep='1'>\n");

    sb.append(
        "<thead><row><entry>Format</entry><entry>Argument</entry><entry>Value</entry>" +
            "<entry>Optional</entry><entry>Description</entry></row></thead>\n");
    sb.append("<tbody>\n");

    for (Entry<String, List<Argument>> entry : formatArguments.entrySet()) {
      final String format = entry.getKey();
      final List<Argument> arguments = entry.getValue();
      int i = 0;
      for (Argument argument : arguments) {
        sb.append("<row>\n");
        if (i == 0) {
          sb.append("<entry morerows='").append(arguments.size() - 1)
              .append("' valign='middle'>").append(format).append("</entry>\n");
        }
        sb.append("<entry>").append(argument.argument())
            .append("</entry>\n");
        sb.append("<entry>").append(argument.value()).append("</entry>\n");
        sb.append("<entry>").append(yes(argument.optional()))
            .append("</entry>\n");
        sb.append("<entry>").append(firstCaps(argument.description()))
            .append("</entry>\n");
        sb.append("</row>\n");
        i++;
      }

    }

    sb.append("</tbody>\n");
    sb.append("</tgroup></informaltable>\n\n");

  }

  private static void appendHeader(StringBuilder sb) {
    sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
        + "<!DOCTYPE chapter PUBLIC \"-//OASIS//DTD DocBook XML V4.4//EN\"\n"
        + "\"https://docbook.org/xml/4.4/docbookx.dtd\"[\n"
        + "]>\n" + "<!--\n"
        + "Licensed to the Apache Software Foundation (ASF) under one\n"
        + "or more contributor license agreements.  See the NOTICE file\n"
        + "distributed with this work for additional information\n"
        + "regarding copyright ownership.  The ASF licenses this file\n"
        + "to you under the Apache License, Version 2.0 (the\n"
        + "\"License\"); you may not use this file except in compliance\n"
        + "with the License.  You may obtain a copy of the License at\n" + "\n"
        + "   https://www.apache.org/licenses/LICENSE-2.0\n" + "\n"
        + "Unless required by applicable law or agreed to in writing,\n"
        + "software distributed under the License is distributed on an\n"
        + "\"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n"
        + "KIND, either express or implied.  See the License for the\n"
        + "specific language governing permissions and limitations\n"
        + "under the License.\n" + "-->\n" + "\n\n"
        + "<!-- ## Warning ## this content is autogenerated! Please fix issues in to " +
        "opennlp-tools/src/main/java/opennlp/tools/cmdline/GenerateManualTool.java \n"
        + "   and execute the following command in opennlp-tool folder to update this file: \n\n"
        + "      mvn -e -q exec:java \"-Dexec.mainClass=opennlp.tools.cmdline.GenerateManualTool\" "
        + "\"-Dexec.args=../opennlp-docs/src/docbkx/cli.xml\"\n"
        + "-->\n\n" + "<chapter id='tools.cli'>\n\n"
        + "<title>The Command Line Interface</title>\n\n" + "<para>"
        + "This section details the available tools and parameters of the Command Line Interface. "
        + "For a introduction in its usage please refer to <xref linkend='intro.cli'/>.  "
        + "</para>\n\n");
  }

  private static void appendFooter(StringBuilder sb) {
    sb.append("\n\n</chapter>");
  }

  private static String firstCaps(String str) {
    if (str.length() > 1) {
      return str.substring(0, 1).toUpperCase() + str.substring(1);
    } else {
      return str;
    }
  }

  private static String yes(boolean optional) {
    if (optional) {
      return "Yes";
    }
    return "No";
  }

  private static void appendCode(String help, StringBuilder sb) {
    sb.append("<screen>\n" + "<![CDATA[\n").append(splitLongLines(help))
        .append("\n").append("]]>\n").append("</screen> \n");
  }

  /**
   * Prevents long lines. Lines are optimized for printing.
   */
  private static String splitLongLines(String stringBlock) {
    StringBuilder sb = new StringBuilder();
    String line;
    try (BufferedReader reader = new BufferedReader(new StringReader(stringBlock))) {
      while ((line = reader.readLine()) != null) {
        if (line.length() <= MAX_LINE_LENGTH) {
          sb.append(line).append("\n");
        } else {
          StringTokenizer tok = new StringTokenizer(line, " ");
          int lineLen = 0;
          while (tok.hasMoreTokens()) {
            String word = tok.nextToken() + " ";

            if (lineLen + word.length() > MAX_LINE_LENGTH) {
              sb.append("\n        ");
              lineLen = 8;
            }
            sb.append(word);
            lineLen += word.length();
          }
        }
      }
    } catch (Exception e) {
      // nothing to do
    }

    return sb.toString();
  }
}
