/*
 * Copyright (C) Lightbend Inc. <https://www.lightbend.com>
 */

package play.core.server

import java.lang.management.ManagementFactory
import java.util.Properties

/**
 * Abstracts a JVM process so it can be mocked for testing or to
 * isolate pseudo-processes within a VM. Code using this class
 * should use the methods in this class instead of methods like
 * `System.getProperties()`, `System.exit()`, etc.
 */
trait ServerProcess {

  /** The ClassLoader that should be used */
  def classLoader: ClassLoader

  /** The command line arguments the process as invoked with */
  def args: Seq[String]

  /** The process's system properties */
  def properties: Properties

  /** Helper for getting properties */
  final def prop(name: String): Option[String] = Option(properties.getProperty(name))

  /** The process's id */
  def pid: Option[String]

  /** Add a hook to run when the process shuts down */
  def addShutdownHook(hook: => Unit): Unit

  /** Exit the process with a message and optional cause and return code */
  def exit(message: String, cause: Option[Throwable] = None, returnCode: Int = -1): Nothing
}

/**
 * A ServerProcess that wraps a real JVM process. Calls have a real
 * effect on the JVM, e.g. `exit` calls `System.exit.`
 */
class RealServerProcess(val args: Seq[String]) extends ServerProcess {
  def classLoader: ClassLoader = Thread.currentThread.getContextClassLoader

  // These properties are used in Prod mode and for the Server in Dev Mode (not the Application).
  def properties: Properties = System.getProperties
  def pid: Option[String] = {
    ManagementFactory.getRuntimeMXBean.getName.split('@').headOption
  }
  def addShutdownHook(hook: => Unit): Unit = {
    Runtime.getRuntime.addShutdownHook(new Thread {
      override def run() = hook
    })
  }
  def exit(message: String, cause: Option[Throwable] = None, returnCode: Int = -1): Nothing = {
    System.err.println(message)
    cause.foreach(_.printStackTrace())
    // this System.exit is using a return code and could also cause CoordinatedShutdown to run.
    System.exit(returnCode)
    // Code never reached, but throw an exception to give a type of Nothing
    throw new Exception("SystemProcess.exit called")
  }
}
