// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.sdk.add

import com.intellij.execution.ExecutionException
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBCheckBox
import com.intellij.util.PathUtil
import com.intellij.util.SystemProperties
import com.intellij.util.ui.FormBuilder
import com.jetbrains.python.PyBundle
import com.jetbrains.python.packaging.PyCondaPackageManagerImpl
import com.jetbrains.python.packaging.PyCondaPackageService
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.sdk.associateWithModule
import com.jetbrains.python.sdk.basePath
import com.jetbrains.python.sdk.createSdkByGenerateTask
import icons.PythonIcons
import org.jetbrains.annotations.SystemIndependent
import java.awt.BorderLayout
import java.io.File
import javax.swing.Icon
import javax.swing.JComboBox
import javax.swing.event.DocumentEvent

/**
 * @author vlan
 */
class PyAddNewCondaEnvPanel(private val project: Project?,
                            private val module: Module?,
                            private val existingSdks: List<Sdk>,
                            newProjectPath: String?,
                            context: UserDataHolder) : PyAddNewEnvPanel() {
  override val envName: String = "Conda"
  override val panelName: String = "New environment"
  override val icon: Icon = PythonIcons.Python.Anaconda

  private val languageLevelsField: JComboBox<String>
  private val condaPathField = TextFieldWithBrowseButton().apply {
    val path = PyCondaPackageService.getInstance().PREFERRED_CONDA_PATH ?: PyCondaPackageService.getSystemCondaExecutable()
    path?.let {
      text = it
    }
    addBrowseFolderListener(PyBundle.message("python.sdk.select.conda.path.title"), null, project,
                            FileChooserDescriptorFactory.createSingleFileOrExecutableAppDescriptor())
    textField.document.addDocumentListener(object : DocumentAdapter() {
      override fun textChanged(e: DocumentEvent) {
        updatePathField()
      }
    })
  }
  private val pathField = TextFieldWithBrowseButton().apply {
    addBrowseFolderListener(PyBundle.message("python.sdk.select.location.for.conda.title"), null, project,
                            FileChooserDescriptorFactory.createSingleFolderDescriptor())
  }
  private val makeSharedField = JBCheckBox(PyBundle.message("available.to.all.projects"))

  override var newProjectPath: String? = newProjectPath
    set(value) {
      field = value
      updatePathField()
    }

  init {
    layout = BorderLayout()

    // https://conda.io/docs/user-guide/install/index.html#system-requirements
    val supportedLanguageLevels = LanguageLevel.SUPPORTED_LEVELS.asReversed().filter { it != LanguageLevel.PYTHON38 }.map { it.toString() }

    languageLevelsField = ComboBox(supportedLanguageLevels.toTypedArray()).apply {
      selectedItem = if (itemCount > 0) getItemAt(0) else null
    }

    updatePathField()

    val formPanel = FormBuilder.createFormBuilder()
      .addLabeledComponent("Location:", pathField)
      .addLabeledComponent("Python version:", languageLevelsField)
      .addLabeledComponent(PyBundle.message("python.sdk.conda.path"), condaPathField)
      .addComponent(makeSharedField)
      .panel
    add(formPanel, BorderLayout.NORTH)
  }

  override fun validateAll(): List<ValidationInfo> =
    listOfNotNull(validateAnacondaPath(), validateEnvironmentDirectoryLocation(pathField))

  override fun getOrCreateSdk(): Sdk? {
    val condaPath = condaPathField.text
    val task = object : Task.WithResult<String, ExecutionException>(project, PyBundle.message("python.sdk.creating.conda.environment.title"), false) {
      override fun compute(indicator: ProgressIndicator): String {
        indicator.isIndeterminate = true
        return PyCondaPackageManagerImpl.createVirtualEnv(condaPath, pathField.text, selectedLanguageLevel)
      }
    }
    val shared = makeSharedField.isSelected
    val associatedPath = if (!shared) projectBasePath else null
    val sdk = createSdkByGenerateTask(task, existingSdks, null, associatedPath, null) ?: return null
    if (!shared) {
      sdk.associateWithModule(module, newProjectPath)
    }
    PyCondaPackageService.getInstance().PREFERRED_CONDA_PATH = condaPath
    return sdk
  }

  override fun addChangeListener(listener: Runnable) {
    val documentListener = object : DocumentAdapter() {
      override fun textChanged(e: DocumentEvent) {
        listener.run()
      }
    }
    pathField.textField.document.addDocumentListener(documentListener)
    condaPathField.textField.document.addDocumentListener(documentListener)
  }

  private fun updatePathField() {
    val baseDir = defaultBaseDir ?: "${SystemProperties.getUserHome()}/.conda/envs"
    val dirName = PathUtil.getFileName(projectBasePath ?: "untitled")
    pathField.text = FileUtil.toSystemDependentName("$baseDir/$dirName")
  }

  private fun validateAnacondaPath(): ValidationInfo? {
    val text = condaPathField.text
    val file = File(text)
    val message = when {
      StringUtil.isEmptyOrSpaces(text) -> "Conda executable path is empty"
      !file.exists() -> "Conda executable not found"
      !file.isFile || !file.canExecute() -> "Conda executable path is not an executable file"
      else -> return null
    }
    return ValidationInfo(message)
  }

  private val defaultBaseDir: String?
    get() {
      val conda = condaPathField.text
      val condaFile = LocalFileSystem.getInstance().findFileByPath(conda) ?: return null
      return condaFile.parent?.parent?.findChild("envs")?.path
    }

  private val projectBasePath: @SystemIndependent String?
    get() = newProjectPath ?: module?.basePath ?: project?.basePath

  private val selectedLanguageLevel: String
    get() = languageLevelsField.getItemAt(languageLevelsField.selectedIndex)
}
