/*
 * Copyright 2016 The Closure Compiler Authors.
 *
 * Licensed 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 com.google.javascript.jscomp;


import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableList;
import com.google.common.io.Files;
import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Test compiler by comparing output to data files aka golden files.
 * Always start with the options object from this class.
 */
public class GoldenFileComparer {

  private static final String DATA_DIR =
      "test/"
          + "com/google/javascript/jscomp/testdata/";

  private static ImmutableList<SourceFile> coverageExterns() {
    SourceFile externs = SourceFile.fromCode(
        "externs", "function Symbol() {}; var window; var self;");
      return ImmutableList.of(externs);
  }

  private static String compile(
      List<SourceFile> externs, List<SourceFile> inputs, CompilerOptions options) {
    Compiler compiler = new Compiler();

    List<SourceFile> allExterns = new ArrayList<>();
    allExterns.addAll(externs);
    allExterns.addAll(coverageExterns());

    compiler.compile(allExterns, inputs, options);
    return compiler.toSource();
  }

  private static String readFile(String filePath) throws IOException {
    return Files.asCharSource(new File(filePath), UTF_8).read();
  }

  private static SourceFile readSource(String fileName) throws IOException {
    String source = readFile(toFullPath(fileName));
   return SourceFile.fromCode(fileName, source);
  }

  private static boolean compare(String compiledSource, String goldenSource) {
    String[] compiledLines = compiledSource.split("\n");
    String[] goldenLines = goldenSource.split("\n");

    assertWithMessage("Num lines of compiled code do not match that of expected")
        .that(compiledLines.length)
        .isEqualTo(goldenLines.length);

    // Loop through each line to make it convenient to pin-point the faulty one
    for (int i = 0; i < compiledLines.length; i++) {
      assertWithMessage(
              "Instrumented code does not match expected at line no: "
                  + (i + 1)
                  + "\nExpected:\n"
                  + goldenLines[i]
                  + "\nFound:\n"
                  + compiledLines[i]
                  + "\n")
          .that(compiledLines[i])
          .isEqualTo(goldenLines[i]);
    }

    return true;
  }

  private static String toFullPath(String fileName) {
    return DATA_DIR + fileName;
  }

  private static void compileAndCompare(
      String goldenFileName,
      CompilerOptions options,
      List<SourceFile> sourceFiles,
      List<SourceFile> externsFiles)
      throws Exception {
    String compiledSource = compile(externsFiles, sourceFiles, options);
    String referenceSource = readFile(toFullPath(goldenFileName));
    compare(compiledSource, referenceSource);
  }

  /**
   * Compile one input file and throw if the result does not match golden. Pass options from this
   * class, mutated with desired options
   */
  public static void compileAndCompare(
      String goldenFileName, CompilerOptions options, String sourceFileName) throws Exception {
    List<SourceFile> sourceFiles = ImmutableList.of(readSource(sourceFileName));
    List<SourceFile> externsFiles = ImmutableList.of();
    compileAndCompare(goldenFileName, options, sourceFiles, externsFiles);
  }

  /**
   * Compile two input files and throw if the result does not match golden. Pass options from this
   * class, mutated with desired options
   */
  public static void compileAndCompare(
      String goldenFileName,
      CompilerOptions options,
      String sourceFileName1,
      String sourceFileName2,
      String externsFileName)
      throws Exception {
    // Prepare sources
    List<SourceFile> sourceFiles =
        ImmutableList.of(readSource(sourceFileName1), readSource(sourceFileName2));

    List<SourceFile> externsFiles =
        ImmutableList.of(SourceFile.fromFile(toFullPath(externsFileName)));

    compileAndCompare(goldenFileName, options, sourceFiles, externsFiles);
  }

  /** Always use these options, they set --pretty_print option for easy verification. */
  public static CompilerOptions options() {
    CompilerOptions options = new CompilerOptions();
    options.setLanguageIn(LanguageMode.ECMASCRIPT3);
    // Instrumentation done
    options.setPrettyPrint(true);
    return options;
  }
}
