package org.infinispan.tools;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * @author Tristan Tarrant &lt;tristan@infinispan.org&gt;
 * @since 10.0
 **/
public class ToolUtils {

   public static final String EMPTY = "";

   public static String getBaseFileName(String absoluteFileName) {
      int slash = absoluteFileName.lastIndexOf(File.separatorChar);
      int dot = absoluteFileName.lastIndexOf('.');
      return absoluteFileName.substring(slash + 1, dot);
   }

   public static void printDocument(Document doc, OutputStream out) throws TransformerException {
      TransformerFactory tf = TransformerFactory.newInstance();
      Transformer transformer = tf.newTransformer();
      transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
      transformer.setOutputProperty(OutputKeys.METHOD, "xml");
      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
      transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

      transformer.transform(new DOMSource(doc),
            new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
   }

   /**
    * Given a parent {@link Node}, it search all the children and returns the first one to match the {@code tagName}.
    *
    * @param parent  The parent {@link Node}.
    * @param tagName The tag name to search for.
    * @return The {@link Node} of the first child with {@code tagName} or {@code null} if it does not exist.
    */
   public static Optional<Node> findFirstChildByTagName(Node parent, String tagName) {
      for (Node child = parent.getFirstChild(); child != null; ) {
         if (tagName.equals(child.getLocalName())) {
            return Optional.of(child);
         }
         child = child.getNextSibling();
      }
      return Optional.empty();
   }

   /**
    * Similar to {@link #findFirstChildByTagName(Node, String)} but it uses a {@code fullPath} to search the first child
    * that matches it.
    *
    * @param parent   The parent {@link Node}.
    * @param fullPath The path where to search for the child.
    * @return The {@link Optional} with the {@link Node} instance if found. Otherwise, an empty {@link Optional}.
    */
   public static Optional<Node> findFirstChildByPath(Node parent, String fullPath) {
      String[] paths = fullPath.split("/", 2);
      for (Node child = parent.getFirstChild(); child != null; ) {
         if (paths[0].equals(child.getLocalName())) {
            return paths.length == 2 ?
                  findFirstChildByPath(child, paths[1]) :
                  Optional.of(child);
         }
         child = child.getNextSibling();
      }
      return Optional.empty();
   }

   /**
    * Parses the "licenses.xml" files generated by the Wildfly licenses-plugin (maven projects) or license-reporter
    * (nodejs projects).
    *
    * @param document The {@link Document} to parse.
    * @return A {@link List} with all the {@link Dependency} found.
    */
   public static List<Dependency> parseXMLDependencies(Document document) {
      List<Dependency> dependencyList = new LinkedList<>();
      Node dependencies = document.getElementsByTagName("dependencies").item(0);
      for (Node dependency = dependencies.getFirstChild(); dependency != null; ) {
         if ("dependency".equals(dependency.getLocalName())) {

            Optional<Node> groupIdNode = findFirstChildByTagName(dependency, "groupId");
            if (!groupIdNode.isPresent()) {
               groupIdNode = findFirstChildByTagName(dependency, "packageName");
            }
            //groupId/packageName must be present all the time
            //noinspection OptionalGetWithoutIsPresent
            String groupId = groupIdNode.map(ToolUtils::textFromNode).get();
            String artifactId = findFirstChildByTagName(dependency, "artifactId")
                  .map(ToolUtils::textFromNode)
                  .orElse(EMPTY);
            String version = findFirstChildByTagName(dependency, "version")
                  .map(ToolUtils::textFromNode)
                  .orElse(EMPTY);
            // Only include artifact if not in inclusive mode or if it's one of the included jars
            dependencyList.add(new Dependency(groupId, artifactId, version, dependency));
         }
         dependency = dependency.getNextSibling();
      }
      return dependencyList;
   }

   /**
    * Trims all the empty lines from a file.
    *
    * @param file The {@link File} to trim.
    * @throws IOException If an i/o error occurs.
    */
   public static void removeEmptyLinesFromFile(File file) throws IOException {
      //it assumes a small file!
      List<String> lines = Files.lines(file.toPath())
            .filter(s -> !s.trim().isEmpty())
            .collect(Collectors.toList());
      try (FileWriter writer = new FileWriter(file)) {
         for (String line : lines) {
            writer.write(line);
            writer.write(System.lineSeparator());
         }
      }
   }

   /**
    * @return the text context trimmed of {@link Node}
    */
   public static String textFromNode(Node node) {
      assert node != null;
      return node.getTextContent().trim();
   }

}
