package sbt.inc

import xsbti.api.SourceAPI
import xsbt.api.ShowAPI
import xsbt.api.DefaultShowAPI._
import java.lang.reflect.Method
import java.util.{ List => JList }

/**
 * A class which computes diffs (unified diffs) between two textual representations of an API.
 *
 * Internally, it uses java-diff-utils library but it calls it through reflection so there's
 * no hard dependency on java-diff-utils.
 *
 * The reflective lookup of java-diff-utils library is performed in the constructor. Exceptions
 * thrown by reflection are passed as-is to the caller of the constructor.
 *
 * @throws ClassNotFoundException if difflib.DiffUtils class cannot be located
 * @throws LinkageError
 * @throws ExceptionInInitializerError
 */
private[inc] class APIDiff {

  import APIDiff._

  private val diffUtilsClass = Class.forName(diffUtilsClassName)
  // method signature: diff(List<?>, List<?>)
  private val diffMethod: Method =
    diffUtilsClass.getMethod(diffMethodName, classOf[JList[_]], classOf[JList[_]])

  private val generateUnifiedDiffMethod: Method = {
    val patchClass = Class.forName(patchClassName)
    // method signature: generateUnifiedDiff(String, String, List<String>, Patch, int)
    diffUtilsClass.getMethod(generateUnifiedDiffMethodName, classOf[String],
      classOf[String], classOf[JList[String]], patchClass, classOf[Int])
  }

  /**
   * Generates an unified diff between textual representations of `api1` and `api2`.
   */
  def generateApiDiff(fileName: String, api1: SourceAPI, api2: SourceAPI, contextSize: Int): String = {
    val api1Str = ShowAPI.show(api1)
    val api2Str = ShowAPI.show(api2)
    generateApiDiff(fileName, api1Str, api2Str, contextSize)
  }

  private def generateApiDiff(fileName: String, f1: String, f2: String, contextSize: Int): String = {
    assert((diffMethod != null) && (generateUnifiedDiffMethod != null), "APIDiff isn't properly initialized.")
    import scala.collection.JavaConverters._
    def asJavaList[T](it: Iterator[T]): java.util.List[T] = it.toSeq.asJava
    val f1Lines = asJavaList(f1.lines)
    val f2Lines = asJavaList(f2.lines)
    //val diff = DiffUtils.diff(f1Lines, f2Lines)
    val diff /*: Patch*/ = diffMethod.invoke(null, f1Lines, f2Lines)
    val unifiedPatch: JList[String] = generateUnifiedDiffMethod.invoke(null, fileName, fileName, f1Lines, diff,
      (contextSize: java.lang.Integer)).asInstanceOf[JList[String]]
    unifiedPatch.asScala.mkString("\n")
  }

}

private[inc] object APIDiff {
  private val diffUtilsClassName = "difflib.DiffUtils"
  private val patchClassName = "difflib.Patch"
  private val diffMethodName = "diff"
  private val generateUnifiedDiffMethodName = "generateUnifiedDiff"
}