package xyz.brysonsteck.ServerCraft.controllers

import kotlinx.coroutines.*
import kotlinx.coroutines.javafx.JavaFx
import org.rauschig.jarchivelib.*

import java.io.File
import java.io.IOException
import java.io.BufferedReader
import java.io.InputStreamReader
import java.awt.Checkbox
import java.awt.Desktop
import java.util.Properties
import java.net.URL
import java.net.URI

import javafx.beans.value.ChangeListener
import javafx.beans.value.ObservableValue
import javafx.concurrent.Task
import javafx.beans.property.BooleanProperty
import javafx.collections.FXCollections
import javafx.fxml.FXML
import javafx.fxml.FXMLLoader
import javafx.geometry.Insets
import javafx.scene.control.Button
import javafx.scene.control.ChoiceBox
import javafx.scene.control.Label
import javafx.scene.control.TextField
import javafx.scene.control.Spinner
import javafx.scene.control.TitledPane
import javafx.scene.control.ButtonBar
import javafx.scene.control.CheckBox
import javafx.scene.control.ProgressBar
import javafx.scene.control.Hyperlink
import javafx.scene.control.ScrollPane
import javafx.scene.layout.Border
import javafx.scene.layout.BorderStroke
import javafx.scene.layout.GridPane
import javafx.scene.layout.Pane
import javafx.scene.layout.HBox
import javafx.scene.layout.VBox
import javafx.scene.text.TextAlignment
import javafx.scene.text.Text
import javafx.scene.Scene
import javafx.scene.input.MouseEvent
import javafx.scene.image.Image
import javafx.scene.image.ImageView
import javafx.stage.FileChooser
import javafx.stage.FileChooser.ExtensionFilter
import javafx.stage.DirectoryChooser
import javafx.stage.Modality
import javafx.stage.Stage
import javafx.event.EventHandler
import javafx.event.ActionEvent
import org.rauschig.jarchivelib.*

import xyz.brysonsteck.ServerCraft.server.Server
import xyz.brysonsteck.ServerCraft.server.Download
import xyz.brysonsteck.ServerCraft.App

class PrimaryController {
  @FXML
  lateinit private var primary: Pane
  @FXML
  lateinit private var currentDirectoryLabel: Label
  @FXML
  lateinit private var worldNameField: TextField
  @FXML
  lateinit private var seedField: TextField
  @FXML
  lateinit private var portSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var difficultyBox: ChoiceBox<String>
  @FXML
  lateinit private var gamemodeBox: ChoiceBox<String>
  @FXML
  lateinit private var worldTypeBox: ChoiceBox<String>
  @FXML
  lateinit private var worldSettingsPane: HBox
  @FXML
  lateinit private var parentPane: Pane
  @FXML
  lateinit private var directoryPane: Pane
  @FXML
  lateinit private var buttonBar: ButtonBar
  @FXML
  lateinit private var flightCheckbox: CheckBox
  @FXML
  lateinit private var netherCheckbox: CheckBox
  @FXML
  lateinit private var structuresCheckbox: CheckBox
  @FXML
  lateinit private var pvpCheckbox: CheckBox
  @FXML
  lateinit private var whitelistCheckbox: CheckBox
  @FXML
  lateinit private var cmdBlocksCheckbox: CheckBox
  @FXML
  lateinit private var playerCountCheckbox: CheckBox
  @FXML
  lateinit private var maxPlayerSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var maxSizeSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var memorySpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var spawnSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var simulationSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var renderSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var maxTickSpinner: Spinner<kotlin.Int>
  @FXML
  lateinit private var statusBar: Label
  @FXML
  lateinit private var progressBar: ProgressBar
  @FXML
  lateinit private var startButton: Button
  @FXML
  lateinit private var buildButton: Button
  @FXML
  lateinit private var defaultsButton: Button
  @FXML
  lateinit private var dropDownIcon: ImageView
  @FXML
  lateinit private var console: Label
  @FXML
  lateinit private var scrollPane: ScrollPane

  lateinit private var server: Server
  private var building = false
  private var directory = ""
  private var asyncResult = false
  private var started = false
  private var loading = false
  private var showingConsole = false

  private fun log(str: String) {
    if (console.text.length > 100000) {
      console.text = console.text.drop(str.length + 10)
    }
    console.text = console.text + str
    print(str)
  }

  @FXML
  public fun initialize() {
    scrollPane.vvalueProperty().bind(console.heightProperty());
    difficultyBox.items = FXCollections.observableArrayList(
      "Peaceful",
      "Easy",
      "Normal",
      "Hard",
      "Hardcore"
    )
    difficultyBox.value = "Normal"
    difficultyBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("difficulty", difficultyBox.items[new as Int])
      }
    }
    gamemodeBox.items = FXCollections.observableArrayList(
      "Survival",
      "Creative",
      "Adventure",
      "Spectator"
    )
    gamemodeBox.value = "Survival"
    gamemodeBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("gamemode", gamemodeBox.items[new as Int])
      }
    }
    worldTypeBox.items = FXCollections.observableArrayList(
      "Normal",
      "Superflat",
      "Large Biomes",
      "Amplified"
    )
    worldTypeBox.value = "Normal"
    worldTypeBox.selectionModel.selectedIndexProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("level-type", worldTypeBox.items[new as Int])
      }
    }
    maxPlayerSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("max-players", new)
      }
    }
    maxSizeSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("max-world-size", new)
      }
    }
    portSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("server-port", new)
      }
    }
    renderSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("view-distance", new)
      }
    }
    memorySpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("jvm-ram", new)
      }
    }
    spawnSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("spawn-protection", new)
      }
    }
    simulationSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("simulation-distance", new)
      }
    }
    maxTickSpinner.editor.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("max-tick-time", new)
      }
    }
    worldNameField.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("level-name", new)
      }
    }
    seedField.textProperty().addListener { _, _, new -> 
      if (!loading) {
        onPropChange("level-seed", new)
      }
    }
  }
  
  @FXML
  private fun onDirectoryButtonClick() {
    val dirChooser = DirectoryChooser()
    dirChooser.title = "Open a server directory"
    dirChooser.initialDirectory = File(System.getProperty("user.home"))
    val result = dirChooser.showDialog(null)
    if (result != null) {
      currentDirectoryLabel.text = result.absolutePath
      server = Server()
      val res = loadServerDir(result.absolutePath)
      if (res) {
        parentPane.isDisable = false
        worldSettingsPane.isDisable = false
        buildButton.isDisable = false
        defaultsButton.isDisable = false
        applyProps()
        server.dir = result.absolutePath
      } else {
        currentDirectoryLabel.text = "<NONE>"
        parentPane.isDisable = true
        worldSettingsPane.isDisable = true
        startButton.isDisable = true
        buildButton.isDisable = true
        defaultsButton.isDisable = true
      }
    }
  }

  private fun parseBool(bool: String): Boolean {
    if (bool == "true") {
      return true
    }
    return false
  }

  private fun applyProps() {
    loading = true
    flightCheckbox.isSelected = parseBool(server.getProp("allow-flight"))
    netherCheckbox.isSelected = parseBool(server.getProp("allow-nether"))
    structuresCheckbox.isSelected = parseBool(server.getProp("generate-structures"))
    pvpCheckbox.isSelected = parseBool(server.getProp("pvp"))
    whitelistCheckbox.isSelected = parseBool(server.getProp("white-list"))
    cmdBlocksCheckbox.isSelected = parseBool(server.getProp("enable-command-block"))
    playerCountCheckbox.isSelected = parseBool(server.getProp("hide-online-players"))
    maxPlayerSpinner.valueFactory.value = server.getProp("max-players").toIntOrNull()
    maxSizeSpinner.valueFactory.value = server.getProp("max-world-size").toIntOrNull()
    portSpinner.valueFactory.value = server.getProp("server-port").toIntOrNull()
    renderSpinner.valueFactory.value = server.getProp("view-distance").toIntOrNull()
    memorySpinner.valueFactory.value = server.getProp("jvm-ram").toIntOrNull()
    spawnSpinner.valueFactory.value = server.getProp("spawn-protection").toIntOrNull()
    simulationSpinner.valueFactory.value = server.getProp("simulation-distance").toIntOrNull()
    maxTickSpinner.valueFactory.value = server.getProp("max-tick-time").toIntOrNull()
    difficultyBox.value = if (parseBool(server.getProp("hardcore"))) {
      "Hardcore"
    } else {
      server.getProp("difficulty").replaceFirstChar { it.uppercase() }
    }
    gamemodeBox.value = server.getProp("gamemode").replaceFirstChar { it.uppercase() }
    worldTypeBox.value = server.getProp("level-type").removePrefix("minecraft:")
      .split('_').joinToString(" ") { it.replaceFirstChar(Char::uppercaseChar)}
    worldNameField.text = server.getProp("level-name")
    seedField.text = server.getProp("level-seed")
    loading = false
  }

  @FXML
  private fun onCheckboxClick(e: ActionEvent) {
    val box = e.target as CheckBox
    when {
      box == whitelistCheckbox -> {
        server.setProp("white-list", whitelistCheckbox.isSelected)
      }
      box == pvpCheckbox -> {
        server.setProp("pvp", pvpCheckbox.isSelected)
      }
      box == netherCheckbox -> {
        server.setProp("allow-nether", netherCheckbox.isSelected)
      }
      box == cmdBlocksCheckbox -> {
        server.setProp("enable-command-block", cmdBlocksCheckbox.isSelected)
      }
      box == flightCheckbox -> {
        server.setProp("allow-flight", flightCheckbox.isSelected)
      }
      box == structuresCheckbox -> {
        server.setProp("generate-structures", structuresCheckbox.isSelected)
      }
      box == playerCountCheckbox -> {
        server.setProp("hide-online-players", playerCountCheckbox.isSelected)
      }
    }
  }

  private fun onPropChange(prop: String, value: String) {
    when {
      prop == "gamemode" -> {
        server.setProp(prop, value.lowercase())
      }
      prop == "difficulty" -> {
        if (value == "Hardcore") {
          server.setProp("hardcode", "true")
          server.setProp(prop, "hard")
        } else {
          server.setProp("hardcode", "false")
          server.setProp(prop, value.lowercase())
        }
      }
      prop == "level-type" -> {
        server.setProp(prop, "minecraft:" + value.lowercase().replace(" ", "_"))
      }
      else -> {
        server.setProp(prop, value)
      }
    }
  }

  @FXML
  private fun onToggleConsole() {
    // this os variable is a workaround for weird issues on windows
    val os = System.getProperty("os.name").lowercase()
    if (showingConsole) {
      primary.getScene().window.height = if (os.contains("win")) 753.0 else 743.0
      dropDownIcon.image = Image(App().javaClass.getResourceAsStream("icons/arrow_down.png"))
    } else {
      primary.getScene().window.height = if (os.contains("win")) 915.0 else 905.0
      dropDownIcon.image = Image(App().javaClass.getResourceAsStream("icons/arrow_up.png"))
    }
    showingConsole = !showingConsole
  }

  @FXML
  private fun onDefaults() {
    val res = createDialog("info", "Reset settings to defaults?\nThere is NO GOING BACK!")
    if (res) {
      server.loadProps()
      applyProps()
      statusBar.text = "Resetting settings to defaults successful."
    }
  }

  @FXML
  private fun onInfo() {
    val stage = Stage()
    val scene = Scene(FXMLLoader(App().javaClass.getResource("info.fxml")).load(), 398.0, 358.0)
    stage.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
    stage.setResizable(false)
    stage.initModality(Modality.APPLICATION_MODAL);
    stage.title = "About ServerCraft"
    stage.scene = scene
    stage.show()
  }

  @FXML
  private fun onBuild() {
    if (building) {
      building = false
      statusBar.text = "Cancelling..."
      buildButton.isDisable = true
      return;
    }
    building = true
    worldSettingsPane.isDisable = true
    directoryPane.isDisable = true
    parentPane.isDisable = true
    startButton.isDisable = true
    defaultsButton.isDisable = true
    buildButton.text = "Cancel Build"

    @Suppress("OPT_IN_USAGE")
    GlobalScope.launch(Dispatchers.Default) {
      progressBar.isVisible = true
      var javaFile = ""
      var archiver = ArchiverFactory.createArchiver(ArchiveFormat.TAR, CompressionType.GZIP)
      val os = System.getProperty("os.name").lowercase()
      when {
        os.contains("win") -> {
          javaFile = "openjdk-20.0.1_windows-x64_bin.zip"
          archiver = ArchiverFactory.createArchiver(ArchiveFormat.ZIP)
        }
        os.contains("linux") -> {
          javaFile = "openjdk-20.0.1_linux-x64_bin.tar.gz"
        }
        os.contains("mac") -> {
          javaFile = "openjdk-20.0.1_macos-x64_bin.tar.gz"
        }
      }

      // download files
      val downloads = mapOf(
        "Java 20" to "https://download.java.net/java/GA/jdk20.0.1/b4887098932d415489976708ad6d1a4b/9/GPL/${javaFile}",
        "BuildTools" to "https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar",
      )
      val destinations = mapOf (
        "Java 20" to directory + "ServerCraft" + File.separator + "Java" + File.separator,
        "BuildTools" to directory + "ServerCraft" + File.separator + "Spigot" + File.separator
      )
      val spigotBuilt = File(destinations["BuildTools"]).exists()
      val javaExtracted = File(destinations["Java 20"] + "jdk-20.0.1").exists() 
      destinations.forEach {
        File(it.value).mkdir()
      }
      downloads.forEach {
        if (it.key == "Java 20" && javaExtracted) {
          return@forEach
        }
        withContext(Dispatchers.JavaFx){
          statusBar.text = "Downloading ${it.key}..."
          progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
          log("Downloading ${it.key} from ${it.value}\n")
        }
        val download = Download(URL(it.value), destinations[it.key]!!)
        download.start()
        while (download.status == Download.Status.DOWNLOADING) {
          var prog = (download.downloaded.toDouble() / download.contentLength.toDouble())
          // for whatever reason I need to print something to the screen in order for it to update the progress bar
          withContext(Dispatchers.JavaFx) {log("Progress: ${prog * 100}%\n")}
          if (prog >= 0.01) {
            withContext(Dispatchers.JavaFx) {progressBar.progress = prog}
          }
          if (!building) download.status = Download.Status.CANCELLED
          Thread.sleep(300)
        }
        withContext(Dispatchers.JavaFx) {log("Download of ${it.key} complete with status: ${download.status}\n")}
      }

      // extract java archive
      if (building && !javaExtracted) {
        withContext(Dispatchers.JavaFx) {
          progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
          statusBar.text = "Extracting Java archive..."
          log("Extracting Java archive to ${directory + "ServerCraft" + File.separator + "Java"}\n")
        }
        var stream = archiver.stream(File(directory + "ServerCraft" + File.separator + "Java" + File.separator + javaFile))
        val dest = File(directory + "ServerCraft" + File.separator + "Java")
        var entries = 0.0
        while(stream.getNextEntry() != null && building) {
          entries++
        }
        stream = archiver.stream(File(directory + "ServerCraft" + File.separator + "Java" + File.separator + javaFile))
        var entry = stream.getNextEntry()
        var currentEntry = 0.0
        do {
          withContext(Dispatchers.JavaFx) {
            progressBar.progress = currentEntry/entries
            log(entry.name + "\n")
          }
          entry.extract(dest)
          entry = stream.getNextEntry()
          currentEntry++
        } while (entry != null && building)
      }

      if (building) {
        withContext(Dispatchers.JavaFx) {
          progressBar.progress = ProgressBar.INDETERMINATE_PROGRESS
          statusBar.text = "Building Minecraft Server..."
        }
        val builder = ProcessBuilder("${directory}ServerCraft${File.separator}Java${File.separator}jdk-20.0.1${File.separator}bin${File.separator}java", "-jar", "BuildTools.jar", "--rev", "latest", "-o", ".." + File.separator + ".." + File.separator)
        builder.directory(File(directory + "ServerCraft" + File.separator + "Spigot"))
        val proc = builder.start()
        val reader = InputStreamReader(proc.inputStream)
        try {
          var cbuf = CharArray(1000)
          var currentline = 0.0
          while (proc.isAlive) {
            if (!building) {
              proc.destroy()
            }
            withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
            cbuf = CharArray(1000)
            reader.read(cbuf, 0, 1000)
            currentline++
            if (currentline > 15) {
              withContext(Dispatchers.JavaFx) {progressBar.progress = if (spigotBuilt) {currentline/1100.0} else {currentline/14122.0} }
            }
          }
          cbuf = CharArray(1000)
          reader.read(cbuf, 0, 1000)
          withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
        } catch (e: IOException) {
          withContext(Dispatchers.JavaFx) {log("Stream Closed\n")}
        } 
      }

      progressBar.isVisible = false
      withContext(Dispatchers.JavaFx){
        worldSettingsPane.isDisable = false
        directoryPane.isDisable = false
        parentPane.isDisable = false
        defaultsButton.isDisable = false
        buildButton.text = "Build Server"
        buildButton.isDisable = false
        statusBar.text = if (building) {
          findServerJar()
          buildButton.text = "Rebuild Server"
          startButton.isDisable = false
          "Ready."
        } else {
          "Server build cancelled."
        }
        building = false;
      }
    }
  }

  @FXML
  private fun onStart() {
    if (started) {
      createDialog("warning", "You should only kill the server if\nabsolutely necessary. Data loss may occur.\nContinue anyway?", hold=false)
      return;
    }
    if (!File(directory + "eula.txt").exists()) {
      val res = eulaDialog()
      if (res) {
        File(directory + "eula.txt").writeText("eula=true")
      } else {
        return;
      }
    }
    started = true
    statusBar.text = "The Minecraft Server is now running. Shutdown the server to unlock the settings."
    worldSettingsPane.isDisable = true
    directoryPane.isDisable = true
    parentPane.isDisable = true
    buildButton.isDisable = true
    defaultsButton.isDisable = true
    startButton.text = "Kill Server"
    @Suppress("OPT_IN_USAGE")
    GlobalScope.launch(Dispatchers.Default) {
      val builder = ProcessBuilder("${directory}ServerCraft${File.separator}Java${File.separator}jdk-20.0.1${File.separator}bin${File.separator}java", "-Xmx${server.getProp("jvm-ram")}M", "-jar", "${server.jar}")
      builder.directory(File(directory))
      val proc = builder.start()
      val reader = InputStreamReader(proc.inputStream)
      try {
        var cbuf = CharArray(1000)
        reader.read(cbuf, 0, 1000)
        while (proc.isAlive) {
          if (asyncResult) {
            withContext(Dispatchers.JavaFx) {
              statusBar.text = "Killing Minecraft server..."
              startButton.isDisable = true
            }
            proc.destroy()
          }
          withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
          cbuf = CharArray(1000)
          reader.read(cbuf, 0, 1000)
        }
        cbuf = CharArray(1000)
        reader.read(cbuf, 0, 1000)
        withContext(Dispatchers.JavaFx) {log(cbuf.joinToString(separator=""))}
      } catch (e: IOException) {
        withContext(Dispatchers.JavaFx) {log("Stream Closed\n")}
      }
      withContext(Dispatchers.JavaFx) {
        statusBar.text = if (asyncResult) {
          asyncResult = false
          "Server killed."
        } else {
          "Server stopped."
        }
        worldSettingsPane.isDisable = false
        directoryPane.isDisable = false
        parentPane.isDisable = false
        buildButton.isDisable = false
        defaultsButton.isDisable = false
        startButton.isDisable = false
        startButton.text = "Start Server"
        started = false
      }
    } 
  }

  private fun eulaDialog(): Boolean {
    var result = false
    val resources = App().javaClass.getResource("icons/warning.png")
    val dialog = Stage()
    dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
    dialog.setResizable(false)
    dialog.initModality(Modality.APPLICATION_MODAL);
    dialog.title = directory
    val scenePane = Pane()
    val dialogScene = Scene(scenePane, 400.0, 150.0);
    val imagePane = Pane()
    val icon = Image("$resources")
    imagePane.layoutX = 14.0
    imagePane.layoutY = 14.0
    imagePane.scaleX = 0.7
    imagePane.scaleY = 0.7
    imagePane.children.add(ImageView(icon))
    val label = Label("Do you agree to the terms of the Minecraft End User License Agreement?")
    label.isWrapText = true
    label.layoutX = 115.0
    label.layoutY = 40.0
    val buttonBar = ButtonBar()
    buttonBar.buttonOrder = "L+R"
    buttonBar.padding = Insets(10.0, 10.0, 10.0, 10.0)
    buttonBar.layoutX = 0.0
    buttonBar.layoutY = 107.0
    buttonBar.prefWidth = 400.0
    val noButton = Button("I Disagree")
    noButton.onAction = EventHandler<ActionEvent>() {
      result = false
      dialog.hide()
    }
    noButton.isDefaultButton = true
    val yesButton = Button("I Agree")
    yesButton.onAction = EventHandler<ActionEvent>() {
      result = true
      dialog.hide()
    }
    val eula = Button("View EULA")
    eula.onAction = EventHandler<ActionEvent>() {
      val desktop = Desktop.getDesktop()
      if (desktop.isSupported(Desktop.Action.BROWSE)) {
        // most likely running on Windows or macOS
        try {
          desktop.browse(URI("https://account.mojang.com/documents/minecraft_eula"))
        } catch (e: Exception) {
          println(e)
        }
      } else {
        // assume running on linux
        try {
          Runtime.getRuntime().exec("xdg-open https://account.mojang.com/documents/minecraft_eula");
        } catch (e: Exception) {
          println(e)
        }
      }
    }
    ButtonBar.setButtonData(eula, ButtonBar.ButtonData.LEFT)
    ButtonBar.setButtonData(noButton, ButtonBar.ButtonData.RIGHT)
    ButtonBar.setButtonData(yesButton, ButtonBar.ButtonData.RIGHT)
    buttonBar.buttons.add(eula)
    buttonBar.buttons.add(noButton)
    buttonBar.buttons.add(yesButton)
    scenePane.children.addAll(imagePane, label, buttonBar)
    dialog.setScene(dialogScene);
    dialog.showAndWait();
    return result
  }

  private fun createDialog(type: String, msg: String, yes: String = "Yes", no: String = "No", hold: Boolean = true): Boolean {
    var result = false
    val resources = App().javaClass.getResource("icons/$type.png")
    val dialog = Stage()
    dialog.icons.add(Image(App().javaClass.getResourceAsStream("app.png")))
    dialog.setResizable(false)
    dialog.initModality(Modality.APPLICATION_MODAL);
    dialog.title = directory
    val scenePane = Pane()
    val dialogScene = Scene(scenePane, 400.0, 150.0);
    val imagePane = Pane()
    val icon = Image("$resources")
    imagePane.layoutX = 14.0
    imagePane.layoutY = 14.0
    imagePane.scaleX = 0.7
    imagePane.scaleY = 0.7
    imagePane.children.add(ImageView(icon))
    val label = Label(msg)
    label.isWrapText = true
    label.layoutX = 115.0
    label.layoutY = if (type == "warning") {10.0} else {40.0}
    val buttonBar = ButtonBar()
    buttonBar.padding = Insets(10.0, 10.0, 10.0, 10.0)
    buttonBar.layoutX = 0.0
    buttonBar.layoutY = 107.0
    buttonBar.prefWidth = 400.0
    val noButton = Button(no)
    noButton.onAction = EventHandler<ActionEvent>() {
      if (hold) {
        result = false
      } else {
        asyncResult = false
      }
      dialog.hide()
    }
    val yesButton = Button(yes)
    yesButton.onAction = EventHandler<ActionEvent>() {
      if (hold) {
        result = true
      } else {
        asyncResult = true
      }
      dialog.hide()
    }
    yesButton.isDefaultButton = true
    buttonBar.buttons.add(noButton)
    buttonBar.buttons.add(yesButton)
    scenePane.children.addAll(imagePane, label, buttonBar)
    dialog.setScene(dialogScene);
    if (hold) {
      dialog.showAndWait();
    } else {
      dialog.show();
    }
    return result
  }

  private fun loadServerDir(dir: String): Boolean {
    directory = dir
    // exit if doesn't exist for whatever reason
    if (!File(directory).isDirectory) {
      return false;
    }

    // add system dir separator for cleaner code
    if (directory[directory.length-1] != File.separatorChar)
      directory += File.separatorChar

    val hasDummy = File(directory + "ServerCraft").isDirectory
    val hasProperties = File(directory + File.separator + "server.properties").isFile
    val hasServer = findServerJar()

    if (hasDummy && hasServer) {
      // server complete, just read jproperties
      statusBar.text = "Server found!"
      startButton.isDisable = false
      buildButton.text = "Rebuild Server"
    } else if (hasDummy && !hasServer && hasProperties) {
      // just needs to be built
      startButton.isDisable = true
      statusBar.text = "Server needs to be built before starting."
    } else if (!hasDummy && hasServer) {
      // server created externally
      val result = createDialog("warning", "This server directory was not created by \nServerCraft. Errors may occur; copying\nthe world directories to a new folder may be\nsafer. Proceed anyway?")
      statusBar.text = "Ready."
      if (result) {
        startButton.isDisable = false
      }
      server.loadProps(dir)
      return result
    } else {
      // assume clean directory
      val result = createDialog("info", "There is no server in this directory.\nCreate one?")
      if (result) {
        File(directory + "ServerCraft").mkdir()
        startButton.isDisable = true
        buildButton.text = "Build Server"
      }
      statusBar.text = "Ready."
      server.loadProps()
      return result
    }

    if (hasProperties) {
      server.loadProps(dir)
    } else {
      server.loadProps()
    }

    return true;
  }

  private fun findServerJar(): Boolean {
    // search for spigot jar
    // major version
    for (i in 25 downTo 8) {
      // patch number
      for (j in 15 downTo 0) {
        var spigotFile: String = ""
        if (j == 0)
          spigotFile += "spigot-1.$i.jar"
        else
          spigotFile += "spigot-1.$i.$j.jar";

        if (File(directory + spigotFile).isFile) {
          server.jar = directory + spigotFile
          return true
        }
      }
    }

    // try vanilla server if no spigot server
    if (File(directory + "server.jar").isFile) {
      server.jar = directory + "server.jar"
      return true
    }

    return false
  }
}