package org.extendj;


import org.extendj.ast.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

public class ScopeAnalysis extends Frontend {

  public ScopeAnalysis() {
    super("Java Scope Anaysis", ExtendJVersion.getVersion());
  }


  public Program getProgram() {
    return program;
  }

  private Program program;

  /**
   * Entry point for the Java checker.
   *
   * @param args command-line arguments
   */
  public static void main(String[] args) {

    List<String> arguments = new ArrayList<>(Arrays.asList(args));

    boolean debug = arguments.isEmpty() || arguments.remove("--debug");
    boolean tree = arguments.remove("--tree");
    boolean warnings = arguments.remove("--warnings");

    if (arguments.size() > 1) {
      System.out.println("usage: ScopeAnalysis [--debug] [--tree] [--warnings] <directory with java files>");
      System.exit(-1);
    }
    String path = arguments.isEmpty() ? "../testprograms/simpleScope" : arguments.get(arguments.size() - 1);

    if (debug) {
      new ScopeAnalysis().analyze(path, tree, warnings);
    } else {
      new ScopeAnalysis().analyzeTimed(path);
    }

  }

  public void analyzeTimed(String path) {
    try {
      List<String> files = Files.walk(Paths.get(path))
          .filter(Files::isRegularFile)
          .filter(x -> x.getFileName().toString().endsWith(".java")).map(Path::toString).collect(Collectors.toList());

      // measure the time (with parsing) from here
      long startMeasurementTime = System.nanoTime();

      program = readProgram(files);

      // measure the time (without parsing) from here
      long startGenerationTime = System.nanoTime();

      ScopeTree scopeTree = program.scopeTree();

      long startAnalysisTime = System.nanoTime();

      Set<AbstractFinding> findings = scopeTree.variableShadowings();

      // measure the time until here
      long endTime = System.nanoTime();

      System.out.print("java,var,false,"
          + files.size() + ","
          + scopeTree.numScopes() + ","
          + scopeTree.numDeclarations() + ","
          + (scopeTree.numScopes() + scopeTree.numDeclarations()) + ","
          + findings.size() + ",");

      long parseTime = startGenerationTime - startMeasurementTime;
      long generationTime = startAnalysisTime - startGenerationTime;
      long analysisTime = endTime - startAnalysisTime;
      long fullTime = endTime - startMeasurementTime;

      System.out.print((fullTime / 1000000) + ",");
      System.out.print((parseTime / 1000000) + ",");
      System.out.print((generationTime / 1000000) + ",");
      System.out.print((analysisTime / 1000000) + ",");
      System.out.print(((generationTime + analysisTime) / 1000000) + ",");

    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }


  public Set<AbstractFinding> analyze(String path, boolean tree, boolean warnings) {
    try {
      List<String> files = Files.walk(Paths.get(path))
          .filter(Files::isRegularFile)
          .filter(x -> x.getFileName().toString().endsWith(".java")).map(Path::toString).collect(Collectors.toList());

      program = readProgram(files);

      ScopeTree scopeTree = program.scopeTree();

      if (tree) {
        scopeTree.printAST();
      }

      if (warnings) {
        System.out.println("\nExtendJ found the following problems:");
        for (CompilationUnit unit : program.getCompilationUnitList()) {
          for (Problem problem : unit.problems()) {
            System.out.println(problem);
          }
        }
        System.out.println();
      }

      Set<AbstractFinding> findings = scopeTree.variableShadowings();

      System.out.println("\nScope4J found the following problems:");
      for (AbstractFinding finding : findings) {
        System.out.println(finding);
      }
      System.out.println();

      return findings;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private Program readProgram(Collection<String> files) throws IOException {
    System.out.println("Reading " + (files.size() > 10 ? files.size() + " files" : files.toString()));

    Program program = new Program();
    program.resetStatistics();
    program.initBytecodeReader(Program.defaultBytecodeReader());
    program.initJavaParser(Program.defaultJavaParser());

    initOptions();

    for (String file : files) {
      program.addSourceFile(file);
    }

    TypeDecl object = program.lookupType("java.lang", "Object");
    if (object.isUnknown()) {
      // If we try to continue without java.lang.Object, we'll just get a stack overflow
      // in member lookups because the unknown (Object) type would be treated as circular.
      System.err.println("Error: java.lang.Object is missing."
          + " The Java standard library was not found.");
      throw new RuntimeException("exiting with unhandled error!");
    }

    return program;
  }
}