// SPDX-License-Identifier: Apache-2.0

package firrtl.options

import firrtl.AnnotationSeq

import logger.{ClassLogLevelAnnotation, LogClassNamesAnnotation, LogFileAnnotation, LogLevelAnnotation}

import scopt.OptionParser

import java.util.ServiceLoader

/** A utility for working with command line options
  * @param applicationName the application associated with these command line options
  */
class Shell(val applicationName: String) {

  /** Command line argument parser (OptionParser) with modifications */
  protected val parser = new OptionParser[AnnotationSeq](applicationName) with DuplicateHandling with ExceptOnError

  /** Contains all discovered [[RegisteredLibrary]] */
  final lazy val registeredLibraries: Seq[RegisteredLibrary] = {
    val libraries = scala.collection.mutable.ArrayBuffer[RegisteredLibrary]()
    val iter = ServiceLoader.load(classOf[RegisteredLibrary]).iterator()
    while (iter.hasNext) {
      val lib = iter.next()
      libraries += lib
      parser.note(lib.name)
      lib.addOptions(parser)
    }

    libraries.toSeq
  }

  /** Contains all discovered [[RegisteredTransform]] */
  final lazy val registeredTransforms: Seq[RegisteredTransform] = {
    val transforms = scala.collection.mutable.ArrayBuffer[RegisteredTransform]()
    val iter = ServiceLoader.load(classOf[RegisteredTransform]).iterator()
    if (iter.hasNext) { parser.note("FIRRTL Transform Options") }
    while (iter.hasNext) {
      val tx = iter.next()
      transforms += tx
      tx.addOptions(parser)
    }

    transforms.toSeq
  }

  /** The [[AnnotationSeq]] generated from command line arguments
    *
    * This requires lazy evaluation as subclasses will mixin new command
    * line options via methods of [[Shell.parser]]
    */
  def parse(args: Array[String], initAnnos: AnnotationSeq = Seq.empty): AnnotationSeq = {
    registeredTransforms
    registeredLibraries
    parser
      .parse(args, initAnnos.reverse)
      .getOrElse(throw new OptionsException("Failed to parse command line options", new IllegalArgumentException))
      .reverse
  }

  parser.note("Shell Options")
  ProgramArgsAnnotation.addOptions(parser)
  Seq(TargetDirAnnotation, InputAnnotationFileAnnotation, OutputAnnotationFileAnnotation)
    .foreach(_.addOptions(parser))

  parser
    .opt[Unit]("show-registrations")
    .action { (_, c) =>
      val rtString = registeredTransforms.map(r => s"\n  - ${r.getClass.getName}").mkString
      val rlString = registeredLibraries.map(l => s"\n  - ${l.getClass.getName}").mkString

      println(s"""|The following FIRRTL transforms registered command line options:$rtString
                  |The following libraries registered command line options:$rlString""".stripMargin)
      c
    }
    .unbounded()
    .text("print discovered registered libraries and transforms")

  parser.help("help").text("prints this usage text")

  parser.note("Logging Options")
  Seq(LogLevelAnnotation, ClassLogLevelAnnotation, LogFileAnnotation, LogClassNamesAnnotation)
    .foreach(_.addOptions(parser))
}
