/*
 * sbt
 * Copyright 2023, Scala center
 * Copyright 2011 - 2022, Lightbend, Inc.
 * Copyright 2008 - 2010, Mark Harrah
 * Licensed under Apache License 2.0 (see LICENSE)
 */

package sbt

import java.io.{ File, FileFilter as JFileFilter }
import java.lang.reflect.Method

import sbt.io.{ AllPassFilter, NothingFilter }
import scala.jdk.CollectionConverters.*

sealed trait ScriptedRun {
  final def run(
      resourceBaseDirectory: File,
      bufferLog: Boolean,
      tests: Seq[String],
      launcherJar: File,
      javaCommand: String,
      launchOpts: Seq[String],
      prescripted: java.util.List[File],
      instances: Int,
  ): Unit = {
    run(
      resourceBaseDirectory,
      bufferLog,
      tests,
      launcherJar,
      javaCommand,
      launchOpts,
      prescripted,
      instances,
      keepTempDirectory = false,
    )
  }

  final def run(
      resourceBaseDirectory: File,
      bufferLog: Boolean,
      tests: Seq[String],
      launcherJar: File,
      javaCommand: String,
      launchOpts: Seq[String],
      prescripted: java.util.List[File],
      instances: Int,
      keepTempDirectory: Boolean,
  ): Unit = {
    try {
      invoke(
        resourceBaseDirectory,
        bufferLog,
        tests.toArray,
        launcherJar,
        javaCommand,
        launchOpts.toArray,
        prescripted,
        instances,
        keepTempDirectory,
      )
      ()
    } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
  }

  // v4
  final def run(
      resourceBaseDirectory: File,
      bufferLog: Boolean,
      tests: java.util.List[String],
      launcherJar: File,
      javaCommand: String,
      launchOpts: java.util.List[String],
      prescripted: java.util.List[File],
      instances: Int,
      keepTempDirectory: Boolean,
      includeFilter: JFileFilter,
      excludeFilter: JFileFilter,
  ): Unit = {
    try {
      invoke(
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
        instances,
        keepTempDirectory,
        includeFilter,
        excludeFilter,
      )
      ()
    } catch { case e: java.lang.reflect.InvocationTargetException => throw e.getCause }
  }

  protected def invoke(
      resourceBaseDirectory: File,
      bufferLog: java.lang.Boolean,
      tests: Array[String],
      launcherJar: File,
      javaCommand: String,
      launchOpts: Array[String],
      prescripted: java.util.List[File],
      instances: java.lang.Integer,
  ): AnyRef = {
    invoke(
      resourceBaseDirectory,
      bufferLog,
      tests,
      launcherJar,
      javaCommand,
      launchOpts,
      prescripted,
      instances,
      keepTempDirectory = false,
    )
  }

  protected def invoke(
      resourceBaseDirectory: File,
      bufferLog: java.lang.Boolean,
      tests: Array[String],
      launcherJar: File,
      javaCommand: String,
      launchOpts: Array[String],
      prescripted: java.util.List[File],
      instances: java.lang.Integer,
      keepTempDirectory: java.lang.Boolean,
  ): AnyRef

  // v4. Default drops filters and calls V3 invoke so V1/V2/V3 subclasses need not override.
  protected def invoke(
      resourceBaseDirectory: File,
      bufferLog: java.lang.Boolean,
      tests: java.util.List[String],
      launcherJar: File,
      javaCommand: String,
      launchOpts: java.util.List[String],
      prescripted: java.util.List[File],
      instances: java.lang.Integer,
      keepTempDirectory: java.lang.Boolean,
      includeFilter: JFileFilter,
      excludeFilter: JFileFilter,
  ): AnyRef = {
    invoke(
      resourceBaseDirectory,
      bufferLog,
      tests.toArray(Array.empty[String]),
      launcherJar,
      javaCommand,
      launchOpts.toArray(Array.empty[String]),
      prescripted,
      instances,
      keepTempDirectory,
    )
  }

}

object ScriptedRun {

  def of(scriptedTests: AnyRef, batchExecution: Boolean): ScriptedRun = {
    val fCls = classOf[File]
    val bCls = classOf[Boolean]
    val asCls = classOf[Array[String]]
    val sCls = classOf[String]
    val lfCls = classOf[java.util.List[File]]
    val lsCls = classOf[java.util.List[String]]
    val iCls = classOf[Int]
    val ffCls = classOf[JFileFilter]

    val clazz = scriptedTests.getClass
    if (batchExecution)
      try
        new RunInParallelV4(
          scriptedTests,
          clazz.getMethod(
            "runInParallel",
            fCls,
            bCls,
            lsCls,
            fCls,
            sCls,
            lsCls,
            lfCls,
            iCls,
            bCls,
            ffCls,
            ffCls,
          )
        )
      catch {
        case _: NoSuchMethodException =>
          try
            new RunInParallelV3(
              scriptedTests,
              clazz
                .getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls, bCls)
            )
          catch {
            case _: NoSuchMethodException =>
              try
                new RunInParallelV2(
                  scriptedTests,
                  clazz
                    .getMethod("runInParallel", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, iCls)
                )
              catch {
                case _: NoSuchMethodException =>
                  new RunInParallelV1(
                    scriptedTests,
                    clazz.getMethod("runInParallel", fCls, bCls, asCls, fCls, asCls, lfCls, iCls)
                  )
              }
          }
      }
    else
      try
        new RunV4(
          scriptedTests,
          clazz.getMethod(
            "run",
            fCls,
            bCls,
            lsCls,
            fCls,
            sCls,
            lsCls,
            lfCls,
            bCls,
            ffCls,
            ffCls,
          )
        )
      catch {
        case _: NoSuchMethodException =>
          try
            new RunV3(
              scriptedTests,
              clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls, bCls)
            )
          catch {
            case _: NoSuchMethodException =>
              try
                new RunV2(
                  scriptedTests,
                  clazz.getMethod("run", fCls, bCls, asCls, fCls, sCls, asCls, lfCls)
                )
              catch {
                case _: NoSuchMethodException =>
                  new RunV1(
                    scriptedTests,
                    clazz.getMethod("run", fCls, bCls, asCls, fCls, asCls, lfCls)
                  )
              }
          }
      }
  }

  private class RunV1(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: java.lang.Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      run.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        launchOpts,
        prescripted,
      )
  }

  private class RunInParallelV1(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      runInParallel.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        launchOpts,
        prescripted,
        instances,
      )
  }

  private class RunV2(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: java.lang.Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      run.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
      )
  }

  private class RunInParallelV2(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      runInParallel.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
        instances,
      )
  }

  private class RunV3(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: java.lang.Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      run.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
        keepTempDirectory,
      )
  }

  private class RunInParallelV3(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      runInParallel.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
        instances,
        keepTempDirectory,
      )
  }

  private class RunV4(scriptedTests: AnyRef, run: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: java.lang.Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      invoke(
        resourceBaseDirectory,
        bufferLog,
        tests.toList.asJava,
        launcherJar,
        javaCommand,
        launchOpts.toList.asJava,
        prescripted,
        instances,
        keepTempDirectory,
        AllPassFilter,
        NothingFilter,
      )

    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: java.util.List[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: java.util.List[String],
        prescripted: java.util.List[File],
        instances: java.lang.Integer,
        keepTempDirectory: java.lang.Boolean,
        includeFilter: JFileFilter,
        excludeFilter: JFileFilter,
    ): AnyRef =
      run.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
        keepTempDirectory,
        includeFilter,
        excludeFilter,
      )
  }

  private class RunInParallelV4(scriptedTests: AnyRef, runInParallel: Method) extends ScriptedRun {
    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: Array[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: Array[String],
        prescripted: java.util.List[File],
        instances: Integer,
        keepTempDirectory: java.lang.Boolean,
    ): AnyRef =
      invoke(
        resourceBaseDirectory,
        bufferLog,
        tests.toList.asJava,
        launcherJar,
        javaCommand,
        launchOpts.toList.asJava,
        prescripted,
        instances,
        keepTempDirectory,
        AllPassFilter,
        NothingFilter,
      )

    override protected def invoke(
        resourceBaseDirectory: File,
        bufferLog: java.lang.Boolean,
        tests: java.util.List[String],
        launcherJar: File,
        javaCommand: String,
        launchOpts: java.util.List[String],
        prescripted: java.util.List[File],
        instances: Integer,
        keepTempDirectory: java.lang.Boolean,
        includeFilter: JFileFilter,
        excludeFilter: JFileFilter,
    ): AnyRef =
      runInParallel.invoke(
        scriptedTests,
        resourceBaseDirectory,
        bufferLog,
        tests,
        launcherJar,
        javaCommand,
        launchOpts,
        prescripted,
        instances,
        keepTempDirectory,
        includeFilter,
        excludeFilter,
      )
  }

}
