/*
 * Copyright (c) 2008, Aaron Digulla
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Aaron Digulla nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package de.pdark.decentxml;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A little search'n'replace utility for Maven 2 pom.xml files.
 *
 * @author DIGULAA
 */
public class MavenSNR {
  public static void main(String[] args) {
    try {
      MavenSNR obj = new MavenSNR();
      obj.run(args);
    } catch (Throwable t) {
      while (t != null) {
        t.printStackTrace();
        t = t.getCause();
      }
      System.exit(1);
    }
  }

  private List<POMFile> files = new ArrayList<POMFile>();
  private List<String> searchConditions = new ArrayList<String>();
  private List<String> checkPaths = new ArrayList<String>();
  private List<String> printPaths = new ArrayList<String>();
  private List<String> replacePaths = new ArrayList<String>();

  public void run(String[] args) {
    if (args.length == 0)
      throw new IllegalArgumentException(
          "Usage: $0 path-to-pom.xml search1...searchN [ --check check1...checkN ] [ --print"
              + " path1...pathN ] [ --replace replace1..replaceN ]");

    readFile(new File(args[0]));

    collectArgs(args);

    checkFiles();
  }

  /** @param args */
  private void collectArgs(String[] args) {
    int pos = 1;
    while (pos < args.length) {
      if (args[pos].startsWith("--")) break;

      searchConditions.add(args[pos++]);
    }

    if (pos < args.length && "--check".equals(args[pos])) {
      pos++;
      while (pos < args.length) {
        if (args[pos].startsWith("--")) break;

        checkPaths.add(args[pos++]);
      }
    }

    if (pos < args.length && "--print".equals(args[pos])) {
      pos++;
      while (pos < args.length) {
        if (args[pos].startsWith("--")) break;

        printPaths.add(args[pos++]);
      }
    }

    if (pos < args.length && "--replace".equals(args[pos])) {
      pos++;
      while (pos < args.length) {
        replacePaths.add(args[pos++]);
      }
    }
  }

  private void checkFiles() {
    for (POMFile pom : files) {
      if (pom.matches(searchConditions)) {
        System.out.println("--- " + pom.getFile());
        boolean hasErrors = false;
        for (String path : checkPaths) {
          boolean check = pom.check(path);
          hasErrors = hasErrors || !check;
        }
        if (hasErrors) System.out.println("FAILED");
        else System.out.println("OKAY");

        for (String path : printPaths) pom.print(path);

        for (String path : replacePaths) {
          try {
            pom.replace(path);
          } catch (IOException e) {
            throw new RuntimeException(
                "Can't replace in file " + pom.getFile().getAbsolutePath(), e);
          }
        }
      }
    }
  }

  private void readFile(File file) {
    System.out.println("Reading " + file);
    POMFile pom = new POMFile(file);
    files.add(pom);

    File pomDir = file.getParentFile();
    for (String moduleName : pom.getModules()) {
      File moduleDir = new File(pomDir, moduleName);
      File modulePom = new File(moduleDir, "pom.xml");
      readFile(modulePom);
    }
  }

  public static class POMFile {
    private File file;
    private Document doc;

    public POMFile(File file) {
      if (!file.exists())
        throw new IllegalArgumentException("File " + file.getAbsolutePath() + " doesn't exist");

      this.file = file;
      getDoc();
    }

    public boolean replace(String condition) throws IOException {
      int pos = condition.indexOf('=');
      if (pos == -1) {
        System.out.println("ERROR: No replacement text found");
        return false;
      }
      String elementPath = condition.substring(0, pos);
      String expected = condition.substring(pos + 1);

      Element e = doc.getChild(elementPath);
      if (e == null) {
        System.out.println("WARNING: Element " + elementPath + " not found.");
        return false;
      }

      String content = e.getTrimmedText();
      if (expected.equals(content)) {
        System.out.println("INFO: Element already contains replacement text");
        return false;
      }

      e.setText(expected);

      File bak = new File(file.getAbsolutePath() + ".bak");
      File tmp = new File(file.getAbsolutePath() + ".tmp");

      if (tmp.exists())
        if (!tmp.delete()) throw new IOException("Can't delete " + tmp.getAbsolutePath());

      String encoding = doc.getEncoding();
      if (encoding == null) {
        encoding = "utf-8";
      }
      XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream(tmp), encoding));
      try {
        doc.toXML(writer);
      } finally {
        writer.close();
      }

      if (bak.exists())
        if (!bak.delete()) throw new IOException("Can't delete " + bak.getAbsolutePath());
      if (!file.renameTo(bak))
        throw new IOException(
            "Can't rename " + file.getAbsolutePath() + " to " + bak.getAbsolutePath());
      if (!tmp.renameTo(file))
        throw new IOException(
            "Can't rename " + tmp.getAbsolutePath() + " to " + file.getAbsolutePath());

      System.out.println("INFO: Element " + elementPath + " updated.");
      return true;
    }

    public File getFile() {
      return file;
    }

    public void print(String path) {
      boolean printContent = false;
      if (path.endsWith("/*")) {
        printContent = true;
        path = path.substring(0, path.length() - 2);
      }

      Element e = doc.getChild(path);
      if (e == null) System.out.println("Element " + path + " not found");

      if (printContent) System.out.println(e.getTrimmedText());
      else System.out.println(e.toXML());
    }

    public boolean matches(List<String> searchConditions) {
      for (String searchCondition : searchConditions) if (!matches(searchCondition)) return false;

      return true;
    }

    public boolean matches(String condition) {
      int pos = condition.indexOf('=');
      String elementPath = condition;
      String expected = null;
      if (pos != -1) {
        elementPath = condition.substring(0, pos);
        expected = condition.substring(pos + 1);
      }

      Element e = doc.getChild(elementPath);
      if (e == null) return false;
      if (expected == null) return true;

      String content = e.getTrimmedText();
      return expected.equals(content);
    }

    public boolean check(String condition) {
      int pos = condition.indexOf('=');
      String elementPath = condition;
      String expected = "";
      if (pos != -1) {
        elementPath = condition.substring(0, pos);
        expected = condition.substring(pos + 1);
      }
      //            System.out.println (condition);
      //            System.out.println ("expected="+expected);

      Element e = doc.getChild(elementPath);
      if (e == null) {
        System.out.println("ERROR: Can't find element " + elementPath);
        return false;
      }

      String content = e.getTrimmedText();
      if (!expected.equals(content)) {
        System.out.println(
            "ERROR: " + elementPath + ": expected '" + expected + "', found '" + content + "'");
        return false;
      }

      return true;
    }

    public List<String> getModules() {
      Element modules = doc.getRootElement().getChild("modules");
      if (modules == null || !modules.hasChildren()) return Collections.emptyList();

      List<String> result = new ArrayList<String>();
      for (Element module : modules.getChildren("module")) {
        result.add(module.getTrimmedText());
      }

      return result;
    }

    public Document getDoc() {
      if (doc == null) {
        XMLParser parser = new XMLParser();
        try {
          XMLIOSource source = new XMLIOSource(file);
          doc = parser.parse(source);
        } catch (IOException e) {
          throw new RuntimeException(
              "Error opening file " + file.getAbsolutePath() + " for reading", e);
        }
      }

      return doc;
    }
  }
}
