/* Copyright (c) 2013-2017, Jesper Öqvist <jesper.oqvist@cs.lth.se>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. Neither the name of the copyright holder 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 HOLDER 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 org.extendj.ragdoc;

import org.extendj.ast.ASTNode;
import org.extendj.ast.BytecodeReader;
import org.extendj.ast.CompilationUnit;
import org.extendj.ast.Frontend;
import org.extendj.ast.JavaParser;
import org.extendj.ast.Problem;
import org.extendj.ast.Program;
import org.extendj.ast.TypeDecl;
import se.llbit.json.JsonArray;
import se.llbit.json.JsonObject;
import se.llbit.json.JsonValue;
import se.llbit.json.PrettyPrinter;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Collection;
import java.util.Map;

/**
 * Generate API documentation for a JastAdd project.
 *
 * @author Jesper Öqvist <jesper.oqvist@cs.lth.se>
 */
public class RagDocBuilder extends Frontend {
  /** Generate pretty-printed JSON if in debug mode, otherwise minified JSON is generated. */
  private static final boolean DEBUG = false;
  private JsonBuilder jsonBuilder;

  public static void main(String args[]) {
    RagDocBuilder rd = new RagDocBuilder();
    int result = rd.compile(args);
    if (result != EXIT_SUCCESS) {
      System.exit(result);
    }
  }

  /**
   * @return {@code true} if documentation generation succeeds
   */
  public int compile(String[] args) {
    return run(args, Program.defaultBytecodeReader(), Program.defaultJavaParser());
  }

  @Override public int run(String[] args, BytecodeReader reader, JavaParser parser) {
    System.out.println("Analyzing source tree...");
    long start = System.currentTimeMillis();
    int result = super.run(args, reader, parser);
    if (result != 0) {
      return result;
    }
    if (program.options().hasOption("-version")
        || program.options().hasOption("-help")
        || program.options().files().isEmpty()) {
      // Nothing was analyzed.
      return result;
    }
    long time = System.currentTimeMillis() - start;
    System.out.format("Analysis took %.1fs.%n", time / 1000.0);
    try {
      File outputDir;
      if (program.options().hasValueForOption("-d")) {
        outputDir = new File(program.options().getValueForOption("-d"));
      } else {
        outputDir = new File("doc");
      }
      if (!outputDir.isDirectory()) {
        outputDir.mkdir();
      }
      if (!outputDir.isDirectory()) {
        System.err.format("Error: not a valid output directory: '%s'\n", outputDir.getName());
        return EXIT_CONFIG_ERROR;
      }
      System.out.println("Writing ragdoc metadata to: " + outputDir.getPath());
      System.out.println("Writing package summary.");
      outputJson(outputDir, "packages.json", jsonBuilder.packageIndex());

      System.out.println("Writing classes...");
      for (Map.Entry<TypeDecl, JsonObject> entry : jsonBuilder.typemap.entrySet()) {
        TypeDecl type = entry.getKey();
        JsonObject typeJson = entry.getValue();
        JsonArray subtypes = jsonBuilder.subtypesJson(type);
        if (!subtypes.isEmpty()) {
          typeJson.add("subtypes", jsonBuilder.subtypesJson(type));
        }
        String fileName = type.name() + jsonBuilder.typeId(type).substring(1) + ".json";
        outputJson(outputDir, fileName, typeJson);
      }

      System.out.println("Writing source files...");
      for (Map.Entry<String, File> entry : jsonBuilder.sourceFiles.entrySet()) {
        File file = entry.getValue();
        String path = entry.getKey();
        path = path.replace('/', '_').replace('\\', '_').replace('.', '_');
        copyFile(outputDir, path, file);
      }
    } catch (IOException e) {
      e.printStackTrace();
      return EXIT_ERROR;
    }
    return result;
  }

  private void copyFile(File outputDir, String path, File file) throws IOException {
    Files.copy(file.toPath(), new File(outputDir, path).toPath(),
        StandardCopyOption.REPLACE_EXISTING);
  }

  private void outputJson(File dir, String fileName, JsonValue value) throws IOException {
    File outputFile = new File(dir, fileName);
    PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(outputFile)));
    JsonObject data = new JsonObject();
    data.set("data", value);
    if (DEBUG) {
      PrettyPrinter pp = new PrettyPrinter("  ", out);
      data.prettyPrint(pp);
    } else {
      out.print(data.toCompactString());
    }
    out.close();
  }

  @Override protected int processCompilationUnit(CompilationUnit unit) {
    if (unit.fromSource()) {
      try {
        Collection<Problem> parseErrors = unit.parseErrors();
        if (!parseErrors.isEmpty()) {
          processErrors(parseErrors, unit);
          return EXIT_ERROR;
        } else {
          jsonBuilder.addCompilationUnit(unit);
        }
      } catch (Throwable t) {
        System.err.println("Errors:");
        System.err.println("Fatal exception while processing " +
            unit.pathName() + ":");
        t.printStackTrace(System.err);
        return EXIT_ERROR;
      }
    }
    return EXIT_SUCCESS;
  }

  @Override protected String name() {
    return "rd-builder";
  }

  @Override protected String version() {
    return "1.3.0";
  }

  @Override protected void initOptions() {
    super.initOptions();
    program.options().addKeyValueOption("-ragroot");
    program.options().addKeyOption("-excludeGenerated");
  }

  @Override protected int processArgs(String[] args) {
    int result = super.processArgs(args);
    if (result == 0) {
      // TODO add package exclude argument.
      File ragRoot;
      if (program.options().hasValueForOption("-ragroot")) {
        ragRoot = new File(program.options().getValueForOption("-ragroot"));
      } else {
        System.out.println("No ragroot directory specified. Using default.");
        File userDir = new File(System.getProperty("user.dir"));
        if (userDir.isDirectory()) {
          ragRoot = userDir;
        } else {
          ragRoot = new File(".");
        }
      }
      System.out.println("Using ragroot directory: " + ragRoot.getAbsolutePath());
      boolean excludeGenerated = program.options().hasOption("-excludeGenerated");
      jsonBuilder = new JsonBuilder(ragRoot, excludeGenerated);
      ASTNode.jsonBuilder = jsonBuilder;
    }
    return result;
  }

}