// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.actions

import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.executors.DefaultRunExecutor
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
import com.intellij.execution.util.ExecUtil
import com.intellij.ide.BrowserUtil
import com.intellij.notification.NotificationAction
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.text.SemVer
import icons.AwsIcons
import software.amazon.awssdk.services.cloudformation.model.StackSummary
import software.amazon.awssdk.services.lambda.model.PackageType
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.warn
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager
import software.aws.toolkits.jetbrains.core.credentials.getConnectionSettingsOrThrow
import software.aws.toolkits.jetbrains.core.executables.ExecutableInstance
import software.aws.toolkits.jetbrains.core.executables.ExecutableManager
import software.aws.toolkits.jetbrains.core.executables.getExecutable
import software.aws.toolkits.jetbrains.core.getResourceNow
import software.aws.toolkits.jetbrains.services.cloudformation.SamFunction
import software.aws.toolkits.jetbrains.services.cloudformation.resources.CloudFormationResources
import software.aws.toolkits.jetbrains.services.lambda.SyncServerlessAppWarningDialog
import software.aws.toolkits.jetbrains.services.lambda.sam.SamCommon
import software.aws.toolkits.jetbrains.services.lambda.sam.SamExecutable
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateFileUtils.retrieveSamTemplate
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateFileUtils.validateTemplateFile
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils
import software.aws.toolkits.jetbrains.services.lambda.sam.sync.SyncApplicationRunProfile
import software.aws.toolkits.jetbrains.services.lambda.sam.sync.SyncServerlessApplicationDialog
import software.aws.toolkits.jetbrains.services.lambda.sam.sync.SyncServerlessApplicationSettings
import software.aws.toolkits.jetbrains.settings.SamDisplayDevModeWarningSettings
import software.aws.toolkits.jetbrains.settings.SyncSettings
import software.aws.toolkits.jetbrains.settings.relativeSamPath
import software.aws.toolkits.jetbrains.utils.notifyError
import software.aws.toolkits.jetbrains.utils.notifyNoActiveCredentialsError
import software.aws.toolkits.jetbrains.utils.notifySamCliNotValidError
import software.aws.toolkits.resources.message
import software.aws.toolkits.telemetry.LambdaPackageType
import software.aws.toolkits.telemetry.Result
import software.aws.toolkits.telemetry.SamTelemetry
import software.aws.toolkits.telemetry.SyncedResources
import java.net.URI

class SyncServerlessAppAction : AnAction(
    message("serverless.application.sync"),
    null,
    AwsIcons.Resources.SERVERLESS_APP
) {
    override fun actionPerformed(e: AnActionEvent) {
        val project = e.getRequiredData(PlatformDataKeys.PROJECT)

        if (!AwsConnectionManager.getInstance(project).isValidConnectionSettings()) {
            notifyNoActiveCredentialsError(project = project)
            return
        }

        ExecutableManager.getInstance().getExecutable<SamExecutable>().thenAccept { samExecutable ->
            if (samExecutable is ExecutableInstance.InvalidExecutable || samExecutable is ExecutableInstance.UnresolvedExecutable) {
                notifySamCliNotValidError(
                    project = project,
                    content = (samExecutable as ExecutableInstance.BadExecutable).validationError
                )
                LOG.warn { "Invalid SAM CLI Executable" }
                return@thenAccept
            }

            val execVersion = SemVer.parseFromText(samExecutable.version) ?: error("SAM CLI version could not detected")
            val minVersion = SemVer("1.78.0", 1, 78, 0)

            if (!execVersion.isGreaterOrEqualThan(minVersion)) {
                notifyError(
                    message("sam.cli.version.warning"),
                    message(
                        "sam.cli.version.upgrade.required",
                        execVersion.parsedVersion,
                        minVersion.parsedVersion
                    ),
                    project = project,
                    listOf(
                        NotificationAction.createSimple(message("sam.cli.version.upgrade.reason")) {
                            BrowserUtil.browse(
                                URI(
                                    "https://aws.amazon.com/about-aws/whats-new/2023/03/aws-toolkits-jetbrains-vs-code-sam-accelerate/"
                                )
                            )
                        },
                        NotificationAction.createSimple(message("sam.cli.version.upgrade.instructions")) {
                            BrowserUtil.browse(
                                URI(
                                    "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/" +
                                        "manage-sam-cli-versions.html#manage-sam-cli-versions-upgrade"
                                )
                            )
                        }
                    )
                )
                return@thenAccept
            }

            val templateFile = retrieveSamTemplate(e, project) ?: return@thenAccept

            validateTemplateFile(project, templateFile)?.let {
                notifyError(content = it, project = project)
                LOG.warn { it }
                return@thenAccept
            }

            val templateFunctions = SamTemplateUtils.findFunctionsFromTemplate(project, templateFile)
            val hasImageFunctions: Boolean = templateFunctions.any { (it as? SamFunction)?.packageType() == PackageType.IMAGE }
            val lambdaType = if (hasImageFunctions) LambdaPackageType.Image else LambdaPackageType.Zip
            val syncedResourceType = SyncedResources.AllResources

            ProgressManager.getInstance().run(
                object : Task.WithResult<PreSyncRequirements, Exception>(
                    project,
                    message("serverless.application.sync.fetch.stacks.progress.bar"),
                    false
                ) {
                    override fun compute(indicator: ProgressIndicator): PreSyncRequirements {
                        val dockerDoesntExist = try {
                            val processOutput = ExecUtil.execAndGetOutput(GeneralCommandLine("docker", "ps"))
                            processOutput.exitCode != 0
                        } catch (e: Exception) {
                            LOG.warn(e) { "Docker could not be found" }
                            true
                        }

                        val activeStacks = project.getResourceNow(CloudFormationResources.ACTIVE_STACKS, forceFetch = true, useStale = false)
                        return PreSyncRequirements(dockerDoesntExist, activeStacks)
                    }

                    override fun onFinished() {
                        val warningSettings = SamDisplayDevModeWarningSettings.getInstance()
                        runInEdt {
                            if (warningSettings.showDevModeWarning) {
                                if (!SyncServerlessAppWarningDialog(project).showAndGet()) {
                                    SamTelemetry.sync(
                                        project = project,
                                        result = Result.Cancelled,
                                        syncedResources = syncedResourceType,
                                        lambdaPackageType = lambdaType,
                                        version = SamCommon.getVersionString()
                                    )

                                    return@runInEdt
                                }
                            }

                            FileDocumentManager.getInstance().saveAllDocuments()
                            val parameterDialog = SyncServerlessApplicationDialog(project, templateFile, result.activeStacks)

                            if (!parameterDialog.showAndGet()) {
                                SamTelemetry.sync(
                                    project = project,
                                    result = Result.Cancelled,
                                    syncedResources = syncedResourceType,
                                    lambdaPackageType = lambdaType,
                                    version = SamCommon.getVersionString()
                                )
                                return@runInEdt
                            }
                            val settings = parameterDialog.settings()

                            saveSettings(project, templateFile, settings)

                            if (settings.useContainer) {
                                when (result.dockerDoesntExist) {
                                    null -> return@runInEdt
                                    true -> {
                                        Messages.showWarningDialog(message("lambda.debug.docker.not_connected"), message("docker.not.found"))
                                        SamTelemetry.sync(
                                            project = project,
                                            result = Result.Failed,
                                            syncedResources = syncedResourceType,
                                            lambdaPackageType = lambdaType,
                                            version = SamCommon.getVersionString(),
                                            reason = "Docker not available"
                                        )
                                        return@runInEdt
                                    }

                                    else -> {}
                                }
                            }

                            syncApp(templateFile, project, settings, syncedResourceType, lambdaType)
                        }
                    }
                }

            )
        }
    }

    private fun syncApp(
        templateFile: VirtualFile,
        project: Project,
        settings: SyncServerlessApplicationSettings,
        syncedResources: SyncedResources,
        lambdaPackageType: LambdaPackageType
    ) {
        try {
            val templatePath = templateFile.toNioPath()
            val environment = ExecutionEnvironmentBuilder.create(
                project,
                DefaultRunExecutor.getRunExecutorInstance(),
                SyncApplicationRunProfile(project, settings, project.getConnectionSettingsOrThrow(), templatePath)
            ).build()

            environment.runner.execute(environment)
            SamTelemetry.sync(
                project = project,
                result = Result.Succeeded,
                syncedResources = syncedResources,
                lambdaPackageType = lambdaPackageType,
                version = SamCommon.getVersionString()
            )
        } catch (e: Exception) {
            SamTelemetry.sync(
                project = project,
                result = Result.Failed,
                syncedResources = syncedResources,
                lambdaPackageType = lambdaPackageType,
                version = SamCommon.getVersionString()
            )
        }
    }

    private fun saveSettings(project: Project, templateFile: VirtualFile, settings: SyncServerlessApplicationSettings) {
        ModuleUtil.findModuleForFile(templateFile, project)?.let { module ->
            relativeSamPath(module, templateFile)?.let { samPath ->
                SyncSettings.getInstance(module)?.apply {
                    setSamStackName(samPath, settings.stackName)
                    setSamBucketName(samPath, settings.bucket)
                    setSamEcrRepoUri(samPath, settings.ecrRepo)
                    setSamUseContainer(samPath, settings.useContainer)
                    setEnabledCapabilities(samPath, settings.capabilities)
                    setSamTags(samPath, settings.tags)
                    setSamTempParameterOverrides(samPath, settings.parameters)
                }
            }
        }
    }

    companion object {
        private val LOG = getLogger<SyncServerlessAppAction>()
    }
}

data class PreSyncRequirements(
    val dockerDoesntExist: Boolean? = null,
    val activeStacks: List<StackSummary>
)


// Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.actions

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import icons.AwsIcons
import software.aws.toolkits.jetbrains.services.lambda.LambdaHandlerResolver
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateFileUtils.getSamTemplateFile
import software.aws.toolkits.resources.message

class SyncServerlessApplicationAction : AnAction(message("serverless.application.sync"), null, AwsIcons.Resources.SERVERLESS_APP) {
    override fun actionPerformed(e: AnActionEvent) {
        SyncServerlessAppAction().actionPerformed(e)
    }

    override fun update(e: AnActionEvent) {
        super.update(e)
        e.presentation.isEnabledAndVisible = getSamTemplateFile(e) != null &&
            LambdaHandlerResolver.supportedRuntimeGroups().isNotEmpty()
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.actions

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.project.Project
import software.amazon.awssdk.services.lambda.LambdaClient
import software.amazon.awssdk.services.lambda.model.PackageType
import software.aws.toolkits.jetbrains.core.awsClient
import software.aws.toolkits.jetbrains.core.explorer.actions.SingleResourceNodeAction
import software.aws.toolkits.jetbrains.services.lambda.LambdaBuilder
import software.aws.toolkits.jetbrains.services.lambda.LambdaFunction
import software.aws.toolkits.jetbrains.services.lambda.LambdaFunctionNode
import software.aws.toolkits.jetbrains.services.lambda.runtimeGroup
import software.aws.toolkits.jetbrains.services.lambda.toDataClass
import software.aws.toolkits.jetbrains.services.lambda.upload.UpdateFunctionCodeDialog
import software.aws.toolkits.jetbrains.services.lambda.upload.UpdateFunctionConfigDialog
import software.aws.toolkits.resources.message

abstract class UpdateFunctionAction(title: String) : SingleResourceNodeAction<LambdaFunctionNode>(title) {
    override fun actionPerformed(selected: LambdaFunctionNode, e: AnActionEvent) {
        val project = e.getRequiredData(PlatformDataKeys.PROJECT)

        ApplicationManager.getApplication().executeOnPooledThread {
            val client: LambdaClient = project.awsClient()

            // Fetch latest version just in case
            val functionConfiguration = client.getFunction {
                it.functionName(selected.functionName())
            }.configuration()

            val lambdaFunction = functionConfiguration.toDataClass()
            runInEdt {
                showDialog(project, lambdaFunction)
            }
        }
    }

    protected abstract fun showDialog(project: Project, lambdaFunction: LambdaFunction)
}

class UpdateFunctionConfigurationAction : UpdateFunctionAction(message("lambda.function.updateConfiguration.action")) {
    override fun showDialog(project: Project, lambdaFunction: LambdaFunction) {
        UpdateFunctionConfigDialog(project, lambdaFunction).show()
    }
}

class UpdateFunctionCodeAction : UpdateFunctionAction(message("lambda.function.updateCode.action")) {
    override fun update(selected: LambdaFunctionNode, e: AnActionEvent) {
        if (selected.value.packageType == PackageType.IMAGE) {
            return
        }
        if (selected.value.runtime?.runtimeGroup?.let { LambdaBuilder.getInstanceOrNull(it) } != null) {
            return
        }
        e.presentation.isVisible = false
    }

    override fun showDialog(project: Project, lambdaFunction: LambdaFunction) {
        UpdateFunctionCodeDialog(project, lambdaFunction).show()
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.completion

import com.intellij.codeInsight.completion.PrefixMatcher
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import software.aws.toolkits.jetbrains.services.lambda.RuntimeGroupExtensionPointObject

interface HandlerCompletion {

    /**
     * Get a collection of lookup elements with a presentation to show in lookup popup
     *
     * @return [Collection] of [LookupElement]'s to show in a completion popup
     */
    fun getLookupElements(project: Project): Collection<LookupElement>

    /**
     * Define a prefix matcher for a handler completion
     *
     * @param prefix [String] - prefix for a completion string
     * @return [PrefixMatcher] instance with a defined prefix matching
     */
    fun getPrefixMatcher(prefix: String): PrefixMatcher

    companion object : RuntimeGroupExtensionPointObject<HandlerCompletion>(ExtensionPointName("aws.toolkit.lambda.handlerCompletion"))
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.completion

import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
import com.intellij.codeInsight.lookup.CharFilter.Result
import com.intellij.openapi.project.Project
import com.intellij.util.textCompletion.TextCompletionProvider
import software.aws.toolkits.core.lambda.LambdaRuntime
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.info
import software.aws.toolkits.jetbrains.services.lambda.runtimeGroup

class HandlerCompletionProvider(private val project: Project, runtime: LambdaRuntime?) : TextCompletionProvider {

    private val logger = getLogger<HandlerCompletionProvider>()

    private val handlerCompletion: HandlerCompletion? by lazy {
        val runtimeGroup = runtime?.runtimeGroup ?: return@lazy null

        return@lazy HandlerCompletion.getInstanceOrNull(runtimeGroup) ?: let {
            logger.info { "Lambda handler completion provider is not registered for runtime: ${runtimeGroup.id}. Completion is not supported." }
            null
        }
    }

    val isCompletionSupported by lazy { handlerCompletion != null }

    override fun applyPrefixMatcher(result: CompletionResultSet, prefix: String): CompletionResultSet {
        if (!isCompletionSupported) return result

        val completion = handlerCompletion
            ?: throw IllegalStateException("handlerCompletion must be defined if completion is enabled.")

        val prefixMatcher = completion.getPrefixMatcher(prefix)
        return result.withPrefixMatcher(prefixMatcher)
    }

    override fun getAdvertisement(): String? = null

    override fun getPrefix(text: String, offset: Int): String = text

    override fun fillCompletionVariants(parameters: CompletionParameters, prefix: String, result: CompletionResultSet) {
        if (!isCompletionSupported) return

        val lookupElements = handlerCompletion!!.getLookupElements(project)
        result.addAllElements(lookupElements)
        result.stopHere()
    }

    override fun acceptChar(char: Char): Result? {
        if (!isCompletionSupported) return Result.HIDE_LOOKUP

        return when {
            char == ':' || char == '.' || Character.isLetterOrDigit(char) -> Result.ADD_TO_PREFIX
            else -> null
        }
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.deploy

import com.intellij.ui.components.JBCheckBox
import software.amazon.awssdk.services.cloudformation.model.Capability
import software.aws.toolkits.resources.message

enum class CreateCapabilities(val capability: String, val text: String, val toolTipText: String, val defaultEnabled: Boolean) {
    IAM(
        Capability.CAPABILITY_IAM.toString(),
        message("cloudformation.capabilities.iam"),
        message("cloudformation.capabilities.iam.toolTipText"),
        true
    ),
    NAMED_IAM(
        Capability.CAPABILITY_NAMED_IAM.toString(),
        message("cloudformation.capabilities.named_iam"),
        message("cloudformation.capabilities.named_iam.toolTipText"),
        true
    ),
    AUTO_EXPAND(
        Capability.CAPABILITY_AUTO_EXPAND.toString(),
        message("cloudformation.capabilities.auto_expand"),
        message("cloudformation.capabilities.auto_expand.toolTipText"),
        false
    )
}

class CapabilitiesEnumCheckBoxes {
    private val enums = CreateCapabilities.values()
    val checkboxes = enums.map {
        val box = JBCheckBox(it.text)
        box.toolTipText = it.toolTipText
        box
    }

    var selected: List<CreateCapabilities>
        get() = checkboxes.zip(enums).filter { it.first.isSelected }.map { it.second }
        set(values) = checkboxes.zip(values).forEach { it.first.isSelected = true }
}


// Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.deploy

import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.DialogPanel
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.MutableCollectionComboBoxModel
import com.intellij.ui.SimpleListCellRenderer
import com.intellij.ui.layout.applyToComponent
import com.intellij.ui.layout.panel
import com.intellij.ui.layout.selected
import com.intellij.ui.layout.toBinding
import com.intellij.util.text.nullize
import org.jetbrains.annotations.TestOnly
import software.amazon.awssdk.services.cloudformation.CloudFormationClient
import software.amazon.awssdk.services.cloudformation.model.StackSummary
import software.amazon.awssdk.services.cloudformation.model.Tag
import software.amazon.awssdk.services.ecr.EcrClient
import software.amazon.awssdk.services.lambda.model.PackageType
import software.amazon.awssdk.services.s3.S3Client
import software.aws.toolkits.jetbrains.core.awsClient
import software.aws.toolkits.jetbrains.core.help.HelpIds
import software.aws.toolkits.jetbrains.core.map
import software.aws.toolkits.jetbrains.services.cloudformation.CloudFormationTemplate
import software.aws.toolkits.jetbrains.services.cloudformation.Parameter
import software.aws.toolkits.jetbrains.services.cloudformation.SamFunction
import software.aws.toolkits.jetbrains.services.cloudformation.describeStack
import software.aws.toolkits.jetbrains.services.cloudformation.mergeRemoteParameters
import software.aws.toolkits.jetbrains.services.cloudformation.resources.CloudFormationResources
import software.aws.toolkits.jetbrains.services.ecr.CreateEcrRepoDialog
import software.aws.toolkits.jetbrains.services.ecr.resources.EcrResources
import software.aws.toolkits.jetbrains.services.ecr.resources.Repository
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils
import software.aws.toolkits.jetbrains.services.lambda.sam.ValidateSamParameters.validateParameters
import software.aws.toolkits.jetbrains.services.lambda.sam.ValidateSamParameters.validateStackName
import software.aws.toolkits.jetbrains.services.s3.CreateS3BucketDialog
import software.aws.toolkits.jetbrains.services.s3.resources.S3Resources
import software.aws.toolkits.jetbrains.settings.DeploySettings
import software.aws.toolkits.jetbrains.settings.relativeSamPath
import software.aws.toolkits.jetbrains.ui.KeyValueTextField
import software.aws.toolkits.jetbrains.ui.ResourceSelector
import software.aws.toolkits.jetbrains.utils.ui.bindValueToProperty
import software.aws.toolkits.jetbrains.utils.ui.installOnParent
import software.aws.toolkits.jetbrains.utils.ui.toolTipText
import software.aws.toolkits.jetbrains.utils.ui.validationInfo
import software.aws.toolkits.jetbrains.utils.ui.withBinding
import software.aws.toolkits.resources.message
import java.awt.Component

data class DeployServerlessApplicationSettings(
    val stackName: String,
    val bucket: String,
    val ecrRepo: String?,
    val autoExecute: Boolean,
    val parameters: Map<String, String>,
    val tags: Map<String, String>,
    val useContainer: Boolean,
    val capabilities: List<CreateCapabilities>
)

class DeployServerlessApplicationDialog(
    private val project: Project,
    private val templateFile: VirtualFile,
    private val loadResourcesOnCreate: Boolean = true
) : DialogWrapper(project) {
    var useContainer: Boolean = false
    var newStackName: String = ""
    var requireReview: Boolean = false
    var deployType: DeployType = DeployType.CREATE
    var templateParameters: Map<String, String> = emptyMap()
    var tags: Map<String, String> = emptyMap()
    var showImageOptions: Boolean = false

    // non-dsl components
    private val stackSelector = ResourceSelector.builder()
        .resource(CloudFormationResources.ACTIVE_STACKS)
        .awsConnection(project)
        .customRenderer(SimpleListCellRenderer.create("", StackSummary::stackName))
        .apply {
            if (!loadResourcesOnCreate) {
                disableAutomaticLoading()
            }
        }
        .build()

    private val s3BucketSelector = ResourceSelector.builder()
        .resource(S3Resources.LIST_BUCKETS.map { it.name() })
        .awsConnection(project)
        .apply {
            if (!loadResourcesOnCreate) {
                disableAutomaticLoading()
            }
        }
        .build()

    private val ecrRepoSelector = ResourceSelector.builder()
        .resource(EcrResources.LIST_REPOS)
        .awsConnection(project)
        .customRenderer(SimpleListCellRenderer.create("", Repository::repositoryName))
        .apply {
            if (!loadResourcesOnCreate) {
                disableAutomaticLoading()
            }
        }
        .build()

    private val parametersField = KeyValueTextField()
    private val tagsField = KeyValueTextField(message("tags.title"))
    private val capabilitiesSelector = CapabilitiesEnumCheckBoxes()

    private var templateFileParameters = CloudFormationTemplate.parse(project, templateFile).parameters().toList()
    private val module = ModuleUtilCore.findModuleForFile(templateFile, project)
    private val settings: DeploySettings? = module?.let { DeploySettings.getInstance(it) }
    private val samPath: String = module?.let { relativeSamPath(it, templateFile) } ?: templateFile.name
    private val templateFunctions = SamTemplateUtils.findFunctionsFromTemplate(project, templateFile)
    private val hasImageFunctions: Boolean = templateFunctions.any { (it as? SamFunction)?.packageType() == PackageType.IMAGE }

    private val s3Client: S3Client = project.awsClient()
    private val ecrClient: EcrClient = project.awsClient()
    private val cloudFormationClient: CloudFormationClient = project.awsClient()

    init {
        title = message("serverless.application.deploy.title")
        setOKButtonText(message("serverless.application.deploy.action.name"))
        setOKButtonTooltip(message("serverless.application.deploy.action.description"))

        // populate dialog before init
        showImageOptions = hasImageFunctions
        settings?.samStackName(samPath)?.let { stackName ->
            // If the module has been deployed once, select updateStack
            deployType = DeployType.UPDATE
            stackSelector.selectedItem { it.stackName() == stackName }
            // async populate parameters from remote
            refreshTemplateParametersAndTags(stackName)
        } ?: refreshTemplateParametersAndTags()

        s3BucketSelector.selectedItem = settings?.samBucketName(samPath)
        requireReview = !(settings?.samAutoExecute(samPath) ?: true)
        useContainer = (settings?.samUseContainer(samPath) ?: false)
        capabilitiesSelector.selected = settings?.enabledCapabilities(samPath)
            ?: CreateCapabilities.values().filter { it.defaultEnabled }

        init()
    }

    fun settings() = DeployServerlessApplicationSettings(
        // fields should have been validated at this point
        stackName = if (deployType == DeployType.CREATE) {
            newStackName.nullize()
        } else {
            stackSelector.selected()?.stackName()
        } ?: throw RuntimeException(message("serverless.application.deploy.validation.stack.missing")),
        bucket = s3BucketSelector.selected() ?: throw RuntimeException("s3 bucket selected was null"),
        ecrRepo = if (hasImageFunctions) {
            ecrRepoSelector.selected()?.repositoryUri
        } else {
            null
        },
        autoExecute = !requireReview,
        parameters = templateParameters,
        tags = tags,
        useContainer = useContainer,
        capabilities = capabilitiesSelector.selected
    )

    override fun getHelpId(): String = HelpIds.DEPLOY_SERVERLESS_APPLICATION_DIALOG.id

    override fun createCenterPanel() = buildPanel()

    internal fun buildPanel() =
        panel {
            val wideInputSizeGroup = "wideInputSizeGroup"
            // create stack
            buttonGroup {
                row {
                    val createStackButton = radioButton(
                        message("serverless.application.deploy.label.stack.new")
                    )
                        .bindValueToProperty(::deployType.toBinding(), DeployType.CREATE)
                        .toolTipText(message("serverless.application.deploy.tooltip.createStack"))

                    createStackButton.selected.addListener {
                        if (it && deployType != DeployType.CREATE) {
                            deployType = DeployType.CREATE
                            refreshTemplateParametersAndTags()
                        }
                    }

                    textField(::newStackName)
                        .sizeGroup(wideInputSizeGroup)
                        .constraints(growX)
                        .enableIf(createStackButton.selected)
                        .toolTipText(message("serverless.application.deploy.tooltip.createStack"))
                        .withValidationOnApply { field ->
                            if (!field.isEnabled) {
                                null
                            } else {
                                validateStackName(field.text, stackSelector)?.let { field.validationInfo(it) }
                            }
                        }
                }

                // update stack
                row {
                    val updateStackButton = radioButton(
                        message("serverless.application.deploy.label.stack.select"),
                    )
                        .bindValueToProperty(::deployType.toBinding(), DeployType.UPDATE)
                        .toolTipText(message("serverless.application.deploy.tooltip.updateStack"))

                    updateStackButton.selected.addListener {
                        if (it && deployType != DeployType.UPDATE) {
                            deployType = DeployType.UPDATE
                            refreshTemplateParametersAndTags()
                        }
                    }

                    stackSelector()
                        .sizeGroup(wideInputSizeGroup)
                        .constraints(growX)
                        .enableIf(updateStackButton.selected)
                        .withErrorOnApplyIf(message("serverless.application.deploy.validation.stack.missing")) {
                            it.isEnabled && (it.isLoading || it.selected() == null)
                        }
                        .toolTipText(message("serverless.application.deploy.tooltip.updateStack"))
                }.largeGapAfter()
            }

            // stack parameters
            row(message("serverless.application.deploy.template.parameters")) {
                parametersField()
                    .withBinding(::templateParameters.toBinding())
                    .toolTipText(message("serverless.application.deploy.tooltip.template.parameters"))
                    .withValidationOnApply { validateParameters(it, templateFileParameters) }
            }

            // deploy tags
            val tagsString = message("tags.title")
            row(tagsString) {
                tagsField()
                    .withBinding(::tags.toBinding())
            }

            // s3 bucket
            row(message("serverless.application.deploy.label.bucket")) {
                s3BucketSelector()
                    .constraints(growX)
                    .withErrorOnApplyIf(message("serverless.application.deploy.validation.s3.bucket.empty")) { it.isLoading || it.selected() == null }
                    .toolTipText(message("serverless.application.deploy.tooltip.s3Bucket"))

                button(message("serverless.application.deploy.button.bucket.create")) {
                    val bucketDialog = CreateS3BucketDialog(
                        project = project,
                        s3Client = s3Client,
                        parent = it.source as? Component
                    )

                    if (bucketDialog.showAndGet()) {
                        bucketDialog.bucketName().let {
                            s3BucketSelector.reload(forceFetch = true)
                            s3BucketSelector.selectedItem = it
                        }
                    }
                }
            }

            // ecr repo
            val ecrSelectorPanel = panel {
                row(message("serverless.application.deploy.label.repo")) {
                    ecrRepoSelector()
                        .constraints(growX)
                        .withErrorOnApplyIf(message("serverless.application.deploy.validation.ecr.repo.empty")) {
                            it.isLoading || it.selected() == null
                        }
                        .toolTipText(message("serverless.application.deploy.tooltip.ecrRepo"))

                    button(message("serverless.application.deploy.button.bucket.create")) {
                        val ecrDialog = CreateEcrRepoDialog(
                            project = project,
                            ecrClient = ecrClient,
                            parent = it.source as? Component
                        )

                        if (ecrDialog.showAndGet()) {
                            ecrRepoSelector.reload(forceFetch = true)
                            ecrRepoSelector.selectedItem { it.repositoryName == ecrDialog.repoName }
                        }
                    }
                }
            }
            row {
                ecrSelectorPanel(grow)
                    .installOnParent { showImageOptions }
                    .applyToComponent {
                        isVisible = showImageOptions
                    }
            }

            // cfn caps
            row {
                label(message("cloudformation.capabilities"))
                    .toolTipText(message("cloudformation.capabilities.toolTipText"))

                cell(isFullWidth = true) {
                    capabilitiesSelector.checkboxes.forEach {
                        it()
                    }
                }
            }

            // confirmation
            row {
                cell(isFullWidth = true) {
                    checkBox(message("serverless.application.deploy.review_required"), ::requireReview)
                        .toolTipText(message("serverless.application.deploy.tooltip.deploymentConfirmation"))
                }
            }

            // in container
            row {
                cell(isFullWidth = true) {
                    checkBox(message("serverless.application.deploy.use_container"), ::useContainer)
                        .toolTipText(message("lambda.sam.buildInContainer.tooltip"))
                }
            }
        }

    private fun refreshTemplateParametersAndTags(stackName: String? = null) {
        when (deployType.name) {
            DeployType.CREATE.name -> {
                populateParameters(templateFileParameters)
            }

            DeployType.UPDATE.name -> {
                val selectedStackName = stackName ?: stackSelector.selected()?.stackName()
                if (selectedStackName == null) {
                    populateParameters(emptyList())
                } else {
                    cloudFormationClient.describeStack(selectedStackName) {
                        it?.let {
                            runInEdt(ModalityState.any()) {
                                // This check is here in-case createStack was selected before we got this update back
                                // TODO: should really create a queuing pattern here so we can cancel on user-action
                                if (deployType == DeployType.UPDATE) {
                                    populateParameters(templateFileParameters.mergeRemoteParameters(it.parameters()))
                                    populateTags(it.tags())
                                }
                            }
                        } ?: populateParameters(templateFileParameters)
                    }
                }
            }
        }
    }

    @TestOnly
    fun forceUi(
        panel: DialogPanel,
        isCreateStack: Boolean? = null,
        hasImageFunctions: Boolean? = null,
        stacks: List<StackSummary>? = null,
        buckets: List<String>? = null,
        ecrRepos: List<Repository>? = null,
        forceStackName: Boolean = false,
        stackName: String? = null,
        forceBucket: Boolean = false,
        bucket: String? = null,
        forceEcrRepo: Boolean = false,
        ecrRepo: String? = null,
        autoExecute: Boolean? = null,
        useContainer: Boolean? = null
    ) {
        if (stacks != null) {
            stackSelector.model = MutableCollectionComboBoxModel(stacks)
            stackSelector.forceLoaded()
        }

        if (isCreateStack == true) {
            deployType = DeployType.CREATE
        } else if (isCreateStack == false) {
            deployType = DeployType.UPDATE
        }

        if (forceStackName || stackName != null) {
            if (deployType == DeployType.CREATE) {
                newStackName = stackName ?: ""
            } else {
                stackSelector.selectedItem = stacks?.first { it.stackName() == stackName }
            }
        }

        if (buckets != null) {
            s3BucketSelector.model = MutableCollectionComboBoxModel(buckets)
            s3BucketSelector.forceLoaded()
        }

        if (forceBucket || bucket != null) {
            s3BucketSelector.selectedItem = bucket
        }

        if (ecrRepos != null) {
            ecrRepoSelector.model = MutableCollectionComboBoxModel(ecrRepos)
            ecrRepoSelector.forceLoaded()
        }

        if (forceEcrRepo || ecrRepo != null) {
            ecrRepoSelector.selectedItem = ecrRepo
        }

        if (hasImageFunctions != null) {
            showImageOptions = hasImageFunctions
        }

        if (autoExecute != null) {
            requireReview = autoExecute
        }

        if (useContainer != null) {
            this.useContainer = useContainer
        }

        panel.reset()
    }

    // visible for testing
    internal fun populateParameters(parameters: List<Parameter>, templateFileDeclarationOverrides: List<Parameter>? = null) {
        // TODO: would be nice to be able to pipe through the description
        parametersField.envVars = parameters.associate { it.logicalName to (it.defaultValue() ?: "") }
        templateFileParameters = templateFileDeclarationOverrides ?: CloudFormationTemplate.parse(project, templateFile).parameters().toList()
    }

    private fun populateTags(tags: List<Tag>) {
        tagsField.envVars = tags.associate { it.key() to it.value() }
    }

    enum class DeployType {
        CREATE,
        UPDATE
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution

import com.intellij.execution.configurations.RuntimeConfigurationError
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.messages.MessageBus
import com.intellij.util.text.SemVer
import com.intellij.util.text.nullize
import software.aws.toolkits.core.lambda.LambdaArchitecture
import software.aws.toolkits.core.lambda.LambdaRuntime
import software.aws.toolkits.core.utils.tryOrNull
import software.aws.toolkits.jetbrains.services.lambda.runtimeGroup
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils
import software.aws.toolkits.jetbrains.services.lambda.validation.LambdaHandlerEvaluationListener
import software.aws.toolkits.jetbrains.services.lambda.validation.SamCliVersionEvaluationListener
import software.aws.toolkits.resources.message

fun registerConfigValidationListeners(messageBus: MessageBus, parentDisposable: Disposable, validationCompleteCallback: (() -> Unit)) {
    val connect = messageBus.connect(parentDisposable)
    connect.subscribe(
        LambdaHandlerEvaluationListener.TOPIC,
        object : LambdaHandlerEvaluationListener {
            override fun handlerValidationFinished(handlerName: String, isHandlerExists: Boolean) {
                validationCompleteCallback()
            }
        }
    )

    connect.subscribe(
        SamCliVersionEvaluationListener.TOPIC,
        object : SamCliVersionEvaluationListener {
            override fun samVersionValidationFinished(path: String, version: SemVer) {
                validationCompleteCallback()
            }
        }
    )
}

fun resolveLambdaFromHandler(handler: String?, runtime: String?, architecture: String?): ResolvedFunction {
    handler ?: throw RuntimeConfigurationError(message("lambda.run_configuration.no_handler_specified"))
    return ResolvedFunction(handler, runtime.validateSupportedRuntime(), architecture.validateSupportedArchitecture())
}

fun resolveLambdaFromTemplate(project: Project, templatePath: String?, functionName: String?): ResolvedFunction {
    val (templateFile, logicalName) = validateSamTemplateDetails(templatePath, functionName)

    val function = SamTemplateUtils.findFunctionsFromTemplate(project, templateFile)
        .find { it.logicalName == functionName }
        ?: throw RuntimeConfigurationError(
            message(
                "lambda.run_configuration.sam.no_such_function",
                logicalName,
                templateFile.path
            )
        )

    val handler = tryOrNull { function.handler() }
        ?: throw RuntimeConfigurationError(message("lambda.run_configuration.no_handler_specified"))

    val runtimeString = try {
        function.runtime()
    } catch (e: Exception) {
        throw RuntimeConfigurationError(message("cloudformation.missing_property", "Runtime", logicalName))
    }

    val runtime = runtimeString.validateSupportedRuntime()
    val architecture = function.architectures().validateSupportedArchitectures()

    return ResolvedFunction(handler, runtime, architecture)
}

fun validateSamTemplateDetails(templatePath: String?, functionName: String?): Pair<VirtualFile, String> {
    templatePath?.takeUnless { it.isEmpty() }
        ?: throw RuntimeConfigurationError(message("lambda.run_configuration.sam.no_template_specified"))

    functionName ?: throw RuntimeConfigurationError(message("lambda.run_configuration.sam.no_function_specified"))

    val templateFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(templatePath)
        ?: throw RuntimeConfigurationError(message("lambda.run_configuration.sam.template_file_not_found"))

    return templateFile to functionName
}

fun String?.validateSupportedRuntime(): LambdaRuntime {
    val runtimeString = this.nullize() ?: throw RuntimeConfigurationError(message("lambda.run_configuration.no_runtime_specified"))
    val runtime = LambdaRuntime.fromValue(runtimeString)
    if (runtime?.runtimeGroup == null) {
        throw RuntimeConfigurationError(message("lambda.run_configuration.unsupported_runtime", runtimeString))
    }

    return runtime
}

fun List<String>?.validateSupportedArchitectures(): LambdaArchitecture = this?.firstOrNull()?.validateSupportedArchitecture() ?: LambdaArchitecture.DEFAULT

fun String?.validateSupportedArchitecture(): LambdaArchitecture {
    val architectureString = this.nullize() ?: return LambdaArchitecture.DEFAULT
    return LambdaArchitecture.fromValue(architectureString)
        ?: throw RuntimeConfigurationError(message("lambda.run_configuration.unsupported_architecture", architectureString))
}

data class ResolvedFunction(
    var handler: String,
    var runtime: LambdaRuntime,
    var architecture: LambdaArchitecture
)


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution

import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.configurations.RunConfigurationWithSuppressedDefaultDebugAction
import com.intellij.execution.configurations.RuntimeConfigurationError
import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.util.xmlb.annotations.Property
import org.jdom.Element
import software.aws.toolkits.jetbrains.ui.connection.AwsConnectionsRunConfigurationBase
import software.aws.toolkits.jetbrains.ui.connection.BaseAwsConnectionOptions
import software.aws.toolkits.resources.message
import java.nio.charset.StandardCharsets

abstract class LambdaRunConfigurationBase<T : BaseLambdaOptions>(
    project: Project,
    private val configFactory: ConfigurationFactory,
    id: String
) : AwsConnectionsRunConfigurationBase<T>(project, configFactory, id),
    RunConfigurationWithSuppressedDefaultRunAction,
    RunConfigurationWithSuppressedDefaultDebugAction {

    @Suppress("UNCHECKED_CAST")
    final override fun clone(): RunConfiguration {
        val element = Element("toClone")
        writeExternal(element)

        val copy = configFactory.createTemplateConfiguration(project) as LambdaRunConfigurationBase<*>
        copy.name = name
        copy.readExternal(element)

        return copy
    }

    fun useInputFile(inputFile: String?) {
        val inputOptions = serializableOptions.inputOptions
        inputOptions.inputIsFile = true
        inputOptions.input = inputFile
    }

    fun useInputText(input: String?) {
        val inputOptions = serializableOptions.inputOptions
        inputOptions.inputIsFile = false
        inputOptions.input = input
    }

    fun isUsingInputFile() = serializableOptions.inputOptions.inputIsFile

    fun inputSource() = serializableOptions.inputOptions.input

    protected fun checkInput() {
        inputSource()?.let {
            if (!isUsingInputFile() || FileUtil.exists(it)) {
                return
            }
        }
        throw RuntimeConfigurationError(message("lambda.run_configuration.no_input_specified"))
    }

    protected fun resolveInput() = inputSource()?.let {
        if (isUsingInputFile() && it.isNotEmpty()) {
            FileDocumentManager.getInstance().saveAllDocuments()
            try {
                LocalFileSystem.getInstance().refreshAndFindFileByPath(it)
                    ?.contentsToByteArray(false)
                    ?.toString(StandardCharsets.UTF_8)
                    ?: throw RuntimeConfigurationError(
                        message(
                            "lambda.run_configuration.input_file_error",
                            it
                        )
                    )
            } catch (e: Exception) {
                throw RuntimeConfigurationError(message("lambda.run_configuration.input_file_error", it))
            }
        } else {
            it
        }
    } ?: throw RuntimeConfigurationError(message("lambda.run_configuration.no_input_specified"))
}

open class BaseLambdaOptions : BaseAwsConnectionOptions() {
    @get:Property(flat = true) // flat for backwards compat
    var inputOptions = InputOptions()
}

class InputOptions {
    var inputIsFile = false
    var input: String? = null
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution

import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.ConfigurationTypeUtil
import icons.AwsIcons
import software.aws.toolkits.jetbrains.core.help.HelpIds
import software.aws.toolkits.jetbrains.services.lambda.execution.local.LocalLambdaRunConfigurationFactory
import software.aws.toolkits.jetbrains.services.lambda.execution.remote.RemoteLambdaRunConfigurationFactory
import software.aws.toolkits.resources.message

class LambdaRunConfigurationType :
    ConfigurationTypeBase(
        "aws.lambda",
        message("lambda.service_name"),
        message("lambda.run_configuration.description"),
        AwsIcons.Resources.LAMBDA_FUNCTION
    ) {
    init {
        addFactory(LocalLambdaRunConfigurationFactory(this))
        addFactory(RemoteLambdaRunConfigurationFactory(this))
    }

    override fun getHelpTopic(): String = HelpIds.RUN_DEBUG_CONFIGURATIONS_DIALOG.id

    companion object {
        fun getInstance() = ConfigurationTypeUtil.findConfigurationType(LambdaRunConfigurationType::class.java)
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.util.xmlb.annotations.OptionTag
import com.intellij.util.xmlb.annotations.Property
import com.intellij.util.xmlb.annotations.Tag
import software.aws.toolkits.jetbrains.services.lambda.LambdaLimits.DEFAULT_MEMORY_SIZE
import software.aws.toolkits.jetbrains.services.lambda.LambdaLimits.DEFAULT_TIMEOUT
import software.aws.toolkits.jetbrains.services.lambda.execution.BaseLambdaOptions
import software.aws.toolkits.jetbrains.services.lambda.sam.SamOptions

@Tag("LocalLambdaOptions")
class LocalLambdaOptions : BaseLambdaOptions() {
    @get:Property(flat = true) // flat for backwards compat
    var functionOptions = FunctionOptions()

    @get:Property(surroundWithTag = false)
    var samOptions = SamOptions()
    var debugHost = "localhost"
}

@Tag("FunctionOptions")
class FunctionOptions {
    var useTemplate = false
    var platform: String? = null
    var isImage: Boolean = false
    var pathMappings: List<PersistedPathMapping> = listOf()
    var templateFile: String? = null

    @get:OptionTag("logicalFunctionName")
    var logicalId: String? = null
    var runtime: String? = null
    var architecture: String? = null
    var handler: String? = null
    var timeout: Int = DEFAULT_TIMEOUT
    var memorySize: Int = DEFAULT_MEMORY_SIZE
    var environmentVariables: Map<String, String> = linkedMapOf<String, String>()
}

data class PersistedPathMapping(
    var local: String? = null,
    var remote: String? = null
)


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.execution.ExecutionBundle
import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.RefactoringListenerProvider
import com.intellij.execution.configurations.RuntimeConfigurationError
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.components.service
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.options.SettingsEditorGroup
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.listeners.RefactoringElementAdapter
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.util.PathMappingSettings.PathMapping
import com.intellij.util.text.SemVer
import org.jetbrains.concurrency.isPending
import software.amazon.awssdk.services.lambda.LambdaClient
import software.amazon.awssdk.services.lambda.model.Runtime
import software.aws.toolkits.core.ConnectionSettings
import software.aws.toolkits.core.lambda.LambdaArchitecture
import software.aws.toolkits.core.lambda.LambdaRuntime
import software.aws.toolkits.core.lambda.validOrNull
import software.aws.toolkits.jetbrains.core.executables.ExecutableInstance
import software.aws.toolkits.jetbrains.core.executables.ExecutableManager
import software.aws.toolkits.jetbrains.core.executables.getExecutableIfPresent
import software.aws.toolkits.jetbrains.services.cloudformation.SamFunction
import software.aws.toolkits.jetbrains.services.lambda.Lambda.findPsiElementsForHandler
import software.aws.toolkits.jetbrains.services.lambda.LambdaHandlerResolver
import software.aws.toolkits.jetbrains.services.lambda.execution.LambdaRunConfigurationBase
import software.aws.toolkits.jetbrains.services.lambda.execution.LambdaRunConfigurationType
import software.aws.toolkits.jetbrains.services.lambda.execution.ResolvedFunction
import software.aws.toolkits.jetbrains.services.lambda.execution.resolveLambdaFromHandler
import software.aws.toolkits.jetbrains.services.lambda.execution.resolveLambdaFromTemplate
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.HandlerRunSettings
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.ImageDebugSupport
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.ImageTemplateRunSettings
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.LocalLambdaRunSettings
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.SamRunningState
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.SamSettingsEditor
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.SamSettingsRunConfiguration
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.TemplateRunSettings
import software.aws.toolkits.jetbrains.services.lambda.execution.validateSamTemplateDetails
import software.aws.toolkits.jetbrains.services.lambda.minSamVersion
import software.aws.toolkits.jetbrains.services.lambda.runtimeGroup
import software.aws.toolkits.jetbrains.services.lambda.sam.SamCommon
import software.aws.toolkits.jetbrains.services.lambda.sam.SamExecutable
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils
import software.aws.toolkits.jetbrains.services.lambda.validation.LambdaHandlerEvaluationListener
import software.aws.toolkits.jetbrains.services.lambda.validation.LambdaHandlerValidator
import software.aws.toolkits.jetbrains.ui.connection.AwsConnectionSettingsEditor
import software.aws.toolkits.jetbrains.ui.connection.addAwsConnectionEditor
import software.aws.toolkits.resources.message
import java.nio.file.Paths

class LocalLambdaRunConfigurationFactory(configuration: LambdaRunConfigurationType) : ConfigurationFactory(configuration) {
    override fun createTemplateConfiguration(project: Project) = LocalLambdaRunConfiguration(project, this)
    override fun getName(): String = "Local"
    override fun getId(): String = name
}

class LocalLambdaRunConfiguration(project: Project, factory: ConfigurationFactory) :
    LambdaRunConfigurationBase<LocalLambdaOptions>(project, factory, "SAM CLI"),
    RefactoringListenerProvider,
    SamSettingsRunConfiguration {
    private val messageBus = project.messageBus

    override val serializableOptions = LocalLambdaOptions()

    override fun getConfigurationEditor(): SettingsEditor<LocalLambdaRunConfiguration> {
        val group = SettingsEditorGroup<LocalLambdaRunConfiguration>()
        group.addEditor(ExecutionBundle.message("run.configuration.configuration.tab.title"), LocalLambdaRunSettingsEditor(project))
        group.addEditor(message("lambda.run_configuration.sam"), SamSettingsEditor())
        group.addAwsConnectionEditor(AwsConnectionSettingsEditor(project, LambdaClient.SERVICE_NAME))
        return group
    }

    override fun checkConfiguration() {
        resolveRegion()
        resolveCredentials()
        checkInput()
        if (isImage) {
            validateSamTemplateDetails(templateFile(), logicalId())
            checkImageSamVersion()
            checkImageDebugger()
        } else {
            val function = resolveLambdaInfo(project = project, functionOptions = serializableOptions.functionOptions)
            checkRuntimeSamVersion(function.runtime)
            checkArchitectureSamVersion(function.architecture)
            // If we aren't using a template we need to be able to find the handler. If it's a template, we don't care.
            if (!serializableOptions.functionOptions.useTemplate) {
                checkLambdaHandler(function.handler, function.runtime)
            }
        }
    }

    private fun checkImageSamVersion() {
        val executable = getSam()

        SemVer.parseFromText(executable.version)?.let { semVer ->
            if (semVer < SamCommon.minImageVersion) {
                throw RuntimeConfigurationError(message("lambda.image.sam_version_too_low", semVer, SamCommon.minImageVersion))
            }
        }
    }

    private fun checkImageDebugger() {
        imageDebugger() ?: throw RuntimeConfigurationError(message("lambda.image.missing_debugger", rawImageDebugger().toString()))
    }

    private fun checkRuntimeSamVersion(runtime: LambdaRuntime) {
        val executable = getSam()

        runtime.runtimeGroup?.let { runtimeGroup ->
            SemVer.parseFromText(executable.version)?.let { semVer ->
                // TODO: Executable manager should better expose the VersionScheme of the Executable...
                try {
                    runtimeGroup.validateSamVersionForZipDebugging(runtime, semVer)
                } catch (e: Exception) {
                    throw RuntimeConfigurationError(e.message)
                }
            }
        }
    }

    private fun checkArchitectureSamVersion(architecture: LambdaArchitecture) {
        val executable = getSam()

        SemVer.parseFromText(executable.version)?.let { semVer ->
            val architectureMinSam = architecture.minSamVersion()
            if (semVer < architectureMinSam) {
                throw RuntimeConfigurationError(message("sam.executable.minimum_too_low_architecture", architecture, architectureMinSam))
            }
        }
    }

    private fun getSam() = ExecutableManager.getInstance().getExecutableIfPresent<SamExecutable>().let {
        when (it) {
            is ExecutableInstance.Executable -> it
            is ExecutableInstance.InvalidExecutable, is ExecutableInstance.UnresolvedExecutable -> throw RuntimeConfigurationError(
                (it as? ExecutableInstance.BadExecutable)?.validationError
            )
        }
    }

    private fun checkLambdaHandler(handler: String, runtime: LambdaRuntime): LambdaRuntime {
        val handlerValidator = project.service<LambdaHandlerValidator>()
        val sdkRuntime = runtime.toSdkRuntime() ?: throw IllegalStateException("Cannot map runtime $runtime to SDK runtime.")
        val promise = handlerValidator.evaluate(LambdaHandlerValidator.LambdaEntry(project, sdkRuntime, handler))

        if (promise.isPending) {
            promise.then { isValid ->
                messageBus.syncPublisher(LambdaHandlerEvaluationListener.TOPIC).handlerValidationFinished(handler, isValid)
            }
            throw RuntimeConfigurationError(message("lambda.run_configuration.handler.validation.in_progress"))
        }

        val isHandlerValid = promise.blockingGet(0)!!
        if (!isHandlerValid) {
            throw RuntimeConfigurationError(message("lambda.run_configuration.handler_not_found", handler))
        }

        return runtime
    }

    override fun getState(executor: Executor, environment: ExecutionEnvironment): SamRunningState {
        try {
            val options: LocalLambdaRunSettings = if (serializableOptions.functionOptions.useTemplate) {
                if (serializableOptions.functionOptions.isImage) {
                    val (templateFile, logicalName) = validateSamTemplateDetails(templateFile(), logicalId())

                    val resource = SamTemplateUtils
                        .findImageFunctionsFromTemplate(project, templateFile)
                        .firstOrNull { it.logicalName == logicalId() } ?: throw IllegalStateException("Function ${logicalId()} not found in template!")
                    val function = resource as? SamFunction ?: throw IllegalStateException("Image functions must be a SAM function")

                    val debugger = imageDebugger() ?: throw IllegalStateException("No image debugger with ID ${rawImageDebugger()}")

                    val dockerFile = function.dockerFile() ?: "Dockerfile"
                    val dockerFilePath = Paths.get(templateFile.path).parent.resolve(function.codeLocation()).resolve(dockerFile)
                    ImageTemplateRunSettings(
                        templateFile,
                        debugger,
                        logicalName,
                        LocalFileSystem.getInstance().refreshAndFindFileByIoFile(dockerFilePath.toFile())
                            ?: throw IllegalStateException("Unable to get virtual file for path $dockerFilePath"),
                        pathMappings,
                        environmentVariables(),
                        ConnectionSettings(resolveCredentials(), resolveRegion()),
                        serializableOptions.samOptions.copy(),
                        serializableOptions.debugHost,
                        resolveInput()
                    )
                } else {
                    val (templateFile, logicalId) = validateSamTemplateDetails(templateFile(), logicalId())
                    val resolvedFunction = resolveLambdaInfo(project = project, functionOptions = serializableOptions.functionOptions)
                    TemplateRunSettings(
                        templateFile,
                        resolvedFunction.runtime,
                        resolvedFunction.architecture,
                        resolvedFunction.handler,
                        logicalId,
                        environmentVariables(),
                        ConnectionSettings(resolveCredentials(), resolveRegion()),
                        serializableOptions.samOptions.copy(),
                        serializableOptions.debugHost,
                        resolveInput()
                    )
                }
            } else {
                val resolvedFunction = resolveLambdaInfo(project = project, functionOptions = serializableOptions.functionOptions)
                HandlerRunSettings(
                    resolvedFunction.runtime,
                    resolvedFunction.architecture,
                    resolvedFunction.handler,
                    timeout(),
                    memorySize(),
                    environmentVariables(),
                    ConnectionSettings(resolveCredentials(), resolveRegion()),
                    serializableOptions.samOptions.copy(),
                    serializableOptions.debugHost,
                    resolveInput()
                )
            }
            return SamRunningState(environment, options)
        } catch (e: Exception) {
            throw ExecutionException(e.message, e)
        }
    }

    override fun getRefactoringElementListener(element: PsiElement?): RefactoringElementListener? {
        element?.run {
            val handlerResolver = element.language.runtimeGroup?.let { runtimeGroup ->
                LambdaHandlerResolver.getInstanceOrNull(runtimeGroup)
            } ?: return null

            val handlerPsi = handlerPsiElement() ?: return null

            if (PsiTreeUtil.isAncestor(element, handlerPsi, false)) {
                return object : RefactoringElementAdapter() {
                    private val originalHandler = serializableOptions.functionOptions.handler

                    override fun elementRenamedOrMoved(newElement: PsiElement) {
                        handlerResolver.determineHandler(handlerPsi)?.let { newHandler ->
                            serializableOptions.functionOptions.handler = newHandler
                        }
                    }

                    override fun undoElementMovedOrRenamed(newElement: PsiElement, oldQualifiedName: String) {
                        serializableOptions.functionOptions.handler = originalHandler
                    }
                }
            }
        }
        return null
    }

    fun useTemplate(templateLocation: String?, logicalId: String?, runtime: String? = null) {
        val functionOptions = serializableOptions.functionOptions
        functionOptions.useTemplate = true

        functionOptions.templateFile = templateLocation
        functionOptions.logicalId = logicalId

        functionOptions.handler = null
        functionOptions.runtime = runtime
    }

    fun useHandler(runtime: Runtime?, handler: String?) {
        val functionOptions = serializableOptions.functionOptions
        functionOptions.useTemplate = false

        functionOptions.templateFile = null
        functionOptions.logicalId = null

        functionOptions.handler = handler
        functionOptions.runtime = runtime?.toString()
    }

    fun isUsingTemplate() = serializableOptions.functionOptions.useTemplate

    fun templateFile() = serializableOptions.functionOptions.templateFile

    fun logicalId() = serializableOptions.functionOptions.logicalId

    fun handler() = serializableOptions.functionOptions.handler

    fun runtime(): LambdaRuntime? = LambdaRuntime.fromValue(serializableOptions.functionOptions.runtime)

    /*
     * This is only to be called for Image functions, otherwise we do not store the runtime for ZIP based template functions
     * This is one of the things that needs to be cleaned up when we migrate the underlying representation
     */
    fun runtime(runtime: Runtime?) {
        serializableOptions.functionOptions.runtime = runtime?.toString()
    }

    fun runtime(runtime: LambdaRuntime?) {
        serializableOptions.functionOptions.runtime = runtime?.toString()
    }

    fun architecture() = serializableOptions.functionOptions.architecture

    fun architecture(architecture: LambdaArchitecture?) {
        serializableOptions.functionOptions.architecture = architecture?.toString()
    }

    fun imageDebugger(): ImageDebugSupport? = serializableOptions.functionOptions.runtime?.let {
        ImageDebugSupport.debuggers().get(it)
    }

    private fun rawImageDebugger(): String? = serializableOptions.functionOptions.runtime

    fun imageDebugger(imageDebugger: ImageDebugSupport?) {
        serializableOptions.functionOptions.runtime = imageDebugger?.id
    }

    fun timeout() = serializableOptions.functionOptions.timeout

    fun timeout(timeout: Int) {
        serializableOptions.functionOptions.timeout = timeout
    }

    fun memorySize() = serializableOptions.functionOptions.memorySize

    fun memorySize(memorySize: Int) {
        serializableOptions.functionOptions.memorySize = memorySize
    }

    fun environmentVariables() = serializableOptions.functionOptions.environmentVariables

    fun environmentVariables(envVars: Map<String, String>) {
        serializableOptions.functionOptions.environmentVariables = envVars
    }

    /*
     * This is only needed in `getState` during runtime. Although it is persisted, we don't actually
     * care about reading the value back off of disk.
     * TODO when we have a template registry remove this
     */
    var isImage: Boolean
        get() = serializableOptions.functionOptions.isImage
        set(value) {
            serializableOptions.functionOptions.isImage = value
        }

    var pathMappings: List<PathMapping>
        get() = serializableOptions.functionOptions.pathMappings.map { PathMapping(it.local, it.remote) }
        set(list) {
            serializableOptions.functionOptions.pathMappings = list.map { PersistedPathMapping(it.localRoot, it.remoteRoot) }
        }

    override var dockerNetwork: String?
        get() = serializableOptions.samOptions.dockerNetwork
        set(network) {
            serializableOptions.samOptions.dockerNetwork = network
        }

    override var debugHost: String
        get() = serializableOptions.debugHost
        set(host) {
            serializableOptions.debugHost = host
        }

    override var skipPullImage: Boolean
        get() = serializableOptions.samOptions.skipImagePull
        set(skip) {
            serializableOptions.samOptions.skipImagePull = skip
        }

    override var buildInContainer: Boolean
        get() = serializableOptions.samOptions.buildInContainer
        set(useContainer) {
            serializableOptions.samOptions.buildInContainer = useContainer
        }

    override var additionalBuildArgs: String?
        get() = serializableOptions.samOptions.additionalBuildArgs
        set(args) {
            serializableOptions.samOptions.additionalBuildArgs = args
        }

    override var additionalLocalArgs: String?
        get() = serializableOptions.samOptions.additionalLocalArgs
        set(args) {
            serializableOptions.samOptions.additionalLocalArgs = args
        }

    override fun suggestedName(): String? {
        val subName = serializableOptions.functionOptions.logicalId ?: handlerDisplayName()
        return "[${message("lambda.run_configuration.local")}] $subName"
    }

    private fun handlerDisplayName(): String? {
        val handler = serializableOptions.functionOptions.handler ?: return null
        return runtime()
            ?.toSdkRuntime()
            .validOrNull
            ?.runtimeGroup
            ?.let { LambdaHandlerResolver.getInstanceOrNull(it) }
            ?.handlerDisplayName(handler) ?: handler
    }

    private fun resolveLambdaInfo(project: Project, functionOptions: FunctionOptions): ResolvedFunction =
        if (functionOptions.useTemplate) {
            resolveLambdaFromTemplate(
                project = project,
                templatePath = functionOptions.templateFile,
                functionName = functionOptions.logicalId
            )
        } else {
            resolveLambdaFromHandler(
                handler = functionOptions.handler,
                runtime = functionOptions.runtime,
                architecture = functionOptions.architecture
            )
        }

    private fun handlerPsiElement(handler: String? = handler(), runtime: Runtime? = runtime()?.toSdkRuntime()) = try {
        runtime?.let {
            handler?.let {
                findPsiElementsForHandler(project, runtime, handler).firstOrNull()
            }
        }
    } catch (e: Exception) {
        null
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.execution.RunManager
import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.LazyRunConfigurationProducer
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.TestSourcesFilter
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import org.jetbrains.yaml.psi.YAMLKeyValue
import org.jetbrains.yaml.psi.YAMLPsiElement
import software.aws.toolkits.core.region.AwsRegion
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager
import software.aws.toolkits.jetbrains.services.lambda.LambdaHandlerResolver
import software.aws.toolkits.jetbrains.services.lambda.execution.LambdaRunConfigurationType
import software.aws.toolkits.jetbrains.services.lambda.runtimeGroup
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils.functionFromElement

class LocalLambdaRunConfigurationProducer : LazyRunConfigurationProducer<LocalLambdaRunConfiguration>() {
    // Filter all Lambda run CONFIGURATIONS down to only ones that are Lambda SAM for this run producer
    override fun getConfigurationSettingsList(runManager: RunManager): List<RunnerAndConfigurationSettings> =
        super.getConfigurationSettingsList(runManager).filter { it.configuration is LocalLambdaRunConfiguration }

    override fun setupConfigurationFromContext(
        configuration: LocalLambdaRunConfiguration,
        context: ConfigurationContext,
        sourceElement: Ref<PsiElement>
    ): Boolean {
        val element = context.psiLocation ?: return false
        val result = when (val parent = element.parent) {
            is YAMLKeyValue -> setupFromTemplate(parent, configuration)
            else -> setupFromSourceFile(element, context, configuration)
        }
        if (result) {
            configuration.setGeneratedName()
        }
        return result
    }

    override fun isConfigurationFromContext(configuration: LocalLambdaRunConfiguration, context: ConfigurationContext): Boolean {
        val element = context.psiLocation ?: return false
        return when (val parent = element.parent) {
            is YAMLPsiElement -> isFromTemplateContext(parent, configuration)
            else -> isFromSourceFileContext(element, configuration)
        }
    }

    private fun setupFromSourceFile(element: PsiElement, context: ConfigurationContext, configuration: LocalLambdaRunConfiguration): Boolean {
        val runtimeGroup = element.language.runtimeGroup ?: return false
        if (runtimeGroup !in LambdaHandlerResolver.supportedRuntimeGroups()) {
            return false
        }

        if (context.location?.virtualFile?.let { TestSourcesFilter.isTestSources(it, element.project) } == true) {
            return false
        }

        val resolver = LambdaHandlerResolver.getInstance(runtimeGroup)
        val handler = resolver.determineHandler(element) ?: return false

        // In some scenarios, context lacks the module (bug?). If it can't be found ask for it explicitly from the PSI element
        val module = context.module ?: ModuleUtilCore.findModuleForPsiElement(element)
        val runtime = module?.let {
            runtimeGroup.determineRuntime(it)
        } ?: runtimeGroup.determineRuntime(element.project)

        setAccountOptions(configuration)
        configuration.useHandler(runtime?.toSdkRuntime(), handler)

        return true
    }

    private fun setupFromTemplate(element: YAMLPsiElement, configuration: LocalLambdaRunConfiguration): Boolean {
        val file = element.containingFile?.virtualFile?.path ?: return false
        val function = functionFromElement(element) ?: return false
        setAccountOptions(configuration)
        configuration.useTemplate(file, function.logicalName)

        return true
    }

    private fun isFromSourceFileContext(element: PsiElement, configuration: LocalLambdaRunConfiguration): Boolean {
        val runtimeGroup = element.language.runtimeGroup ?: return false
        if (runtimeGroup !in LambdaHandlerResolver.supportedRuntimeGroups()) {
            return false
        }
        val resolver = LambdaHandlerResolver.getInstance(runtimeGroup)
        val handler = resolver.determineHandler(element) ?: return false
        return configuration.handler() == handler
    }

    private fun isFromTemplateContext(element: YAMLPsiElement, configuration: LocalLambdaRunConfiguration): Boolean {
        val templateFile = configuration.templateFile() ?: return false
        val functionName = configuration.logicalId() ?: return false
        val file = element.containingFile?.virtualFile?.path ?: return false
        val function = functionFromElement(element) ?: return false
        return templateFile == file && functionName == function.logicalName
    }

    override fun getConfigurationFactory(): ConfigurationFactory = getFactory()

    companion object {
        fun getFactory() = LambdaRunConfigurationType.getInstance().configurationFactories.first { it is LocalLambdaRunConfigurationFactory }

        fun setAccountOptions(configuration: LocalLambdaRunConfiguration) {
            val project = configuration.project
            val settings = accountSettings(project)

            configuration.credentialProviderId(settings.first)
            configuration.regionId(settings.second?.id)
        }

        private fun accountSettings(project: Project): Pair<String?, AwsRegion?> {
            val settingsManager = AwsConnectionManager.getInstance(project)
            val region = try {
                settingsManager.activeRegion
            } catch (_: Exception) {
                null
            }

            val credentialProviderId = try {
                settingsManager.activeCredentialProvider.id
            } catch (_: Exception) {
                null
            }

            return credentialProviderId to region
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.openapi.components.service
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.util.PathMappingSettings
import software.aws.toolkits.jetbrains.services.lambda.execution.registerConfigValidationListeners
import software.aws.toolkits.jetbrains.services.lambda.execution.validateSupportedArchitecture
import software.aws.toolkits.jetbrains.services.lambda.validation.LambdaHandlerValidator
import software.aws.toolkits.jetbrains.utils.ui.selected
import javax.swing.JComponent

class LocalLambdaRunSettingsEditor(project: Project) : SettingsEditor<LocalLambdaRunConfiguration>() {
    private val view: LocalLambdaRunSettingsEditorPanel

    init {
        // Invalidate Lambda handler caches before opening run configuration to clear all outdated
        // information that might appears after updating document (e.g. updating handler name).
        // This code is executed once when run configuration form is opened.
        project.service<LambdaHandlerValidator>().clearRequests()
        view = LocalLambdaRunSettingsEditorPanel(project)
        registerConfigValidationListeners(project.messageBus, this) { view.invalidateConfiguration() }
    }

    override fun createEditor(): JComponent = view.panel

    override fun resetEditorFrom(configuration: LocalLambdaRunConfiguration) {
        val useTemplate = configuration.isUsingTemplate()
        view.useTemplate = useTemplate
        if (useTemplate) {
            view.templateSettings.setTemplateFile(configuration.templateFile())
            view.templateSettings.selectFunction(configuration.logicalId())
            view.templateSettings.environmentVariables.envVars = configuration.environmentVariables()
            if (view.templateSettings.isImage) {
                view.templateSettings.imageDebuggerModel.selectedItem = configuration.imageDebugger()
                view.templateSettings.pathMappingsTable.setMappingSettings(PathMappingSettings(configuration.pathMappings))
            }
        } else {
            view.rawSettings.runtimeModel.selectedItem = configuration.runtime()
            view.rawSettings.architectureModel.selectedItem = configuration.architecture()?.validateSupportedArchitecture()
            view.rawSettings.handlerPanel.handler.text = configuration.handler() ?: ""
            view.rawSettings.timeoutSlider.value = configuration.timeout()
            view.rawSettings.memorySlider.value = configuration.memorySize()
            view.rawSettings.environmentVariables.envVars = configuration.environmentVariables()
        }

        if (configuration.isUsingInputFile()) {
            view.lambdaInput.inputFile = configuration.inputSource()
        } else {
            view.lambdaInput.inputText = configuration.inputSource()
        }
    }

    override fun applyEditorTo(configuration: LocalLambdaRunConfiguration) {
        if (view.useTemplate) {
            configuration.useTemplate(view.templateSettings.templateFile.text, view.templateSettings.function.selected()?.logicalName)
            configuration.isImage = view.templateSettings.isImage
            configuration.environmentVariables(view.templateSettings.environmentVariables.envVars)
            if (view.templateSettings.isImage) {
                configuration.imageDebugger(view.templateSettings.imageDebugger.selected())
                configuration.pathMappings = view.templateSettings.pathMappingsTable.mappingSettings.pathMappings
            }
        } else {
            configuration.useHandler(view.rawSettings.runtime.selected()?.toSdkRuntime(), view.rawSettings.handlerPanel.handler.text)
            configuration.architecture(view.rawSettings.architecture.selected())
            configuration.timeout(view.rawSettings.timeoutSlider.value)
            configuration.memorySize(view.rawSettings.memorySlider.value)
            configuration.environmentVariables(view.rawSettings.environmentVariables.envVars)
        }

        if (view.lambdaInput.isUsingFile) {
            configuration.useInputFile(view.lambdaInput.inputFile)
        } else {
            configuration.useInputText(view.lambdaInput.inputText)
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.project.Project
import com.intellij.ui.IdeBorderFactory
import com.intellij.ui.components.JBRadioButton
import com.intellij.ui.components.panels.Wrapper
import com.intellij.util.ui.JBUI
import software.aws.toolkits.jetbrains.services.lambda.execution.LambdaInputPanel
import software.aws.toolkits.resources.message
import javax.swing.JCheckBox
import javax.swing.JPanel

class LocalLambdaRunSettingsEditorPanel(private val project: Project) {
    lateinit var panel: JPanel
        private set
    lateinit var invalidator: JCheckBox
        private set
    lateinit var lambdaInputPanel: JPanel
        private set
    lateinit var lambdaInput: LambdaInputPanel
        private set
    private lateinit var raw: JBRadioButton
    private lateinit var template: JBRadioButton
    private lateinit var settings: Wrapper

    val rawSettings = RawSettings(project)
    val templateSettings = TemplateSettings(project)
    var useTemplate: Boolean
        get() = template.isSelected
        set(value) {
            if (value) {
                template.isSelected = true
                settings.setContent(templateSettings.panel)
            } else {
                raw.isSelected = true
                settings.setContent(rawSettings.panel)
            }
        }

    private fun createUIComponents() {
        lambdaInput = LambdaInputPanel(project)
    }

    init {
        template.addActionListener {
            settings.setContent(templateSettings.panel)
            invalidateConfiguration()
        }
        raw.addActionListener {
            settings.setContent(rawSettings.panel)
            invalidateConfiguration()
        }
        // Select template by default
        template.isSelected = true
        settings.setContent(templateSettings.panel)

        lambdaInputPanel.border = IdeBorderFactory.createTitledBorder(message("lambda.input.label"), false, JBUI.emptyInsets())
    }

    fun invalidateConfiguration() {
        runInEdt { invalidator.isSelected = !invalidator.isSelected }
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.CollectionComboBoxModel
import com.intellij.ui.SortedComboBoxModel
import software.aws.toolkits.core.lambda.LambdaArchitecture
import software.aws.toolkits.core.lambda.LambdaRuntime
import software.aws.toolkits.jetbrains.services.lambda.LambdaBuilder
import software.aws.toolkits.jetbrains.services.lambda.LambdaWidgets.lambdaMemory
import software.aws.toolkits.jetbrains.services.lambda.LambdaWidgets.lambdaTimeout
import software.aws.toolkits.jetbrains.services.lambda.RuntimeGroup
import software.aws.toolkits.jetbrains.ui.HandlerPanel
import software.aws.toolkits.jetbrains.ui.KeyValueTextField
import software.aws.toolkits.jetbrains.ui.SliderPanel
import java.util.Comparator
import javax.swing.JComboBox
import javax.swing.JPanel

class RawSettings(private val project: Project) {
    lateinit var panel: JPanel
        private set
    lateinit var handlerPanel: HandlerPanel
        private set
    lateinit var runtime: JComboBox<LambdaRuntime>
        private set
    lateinit var architecture: JComboBox<LambdaArchitecture>
        private set
    lateinit var timeoutSlider: SliderPanel
        private set
    lateinit var memorySlider: SliderPanel
        private set
    lateinit var environmentVariables: KeyValueTextField
        private set
    lateinit var runtimeModel: SortedComboBoxModel<LambdaRuntime>
        private set
    lateinit var architectureModel: CollectionComboBoxModel<LambdaArchitecture>
        private set

    var lastSelectedRuntime: LambdaRuntime? = null

    private fun createUIComponents() {
        runtimeModel = SortedComboBoxModel(compareBy(Comparator.naturalOrder()) { it.toString() })
        architectureModel = CollectionComboBoxModel(LambdaArchitecture.values().toList())
        runtime = ComboBox(runtimeModel)
        architecture = ComboBox(architectureModel)
        handlerPanel = HandlerPanel(project)
        timeoutSlider = lambdaTimeout()
        memorySlider = lambdaMemory()
        environmentVariables = KeyValueTextField()
    }

    init {
        runtime.addActionListener {
            val index = runtime.selectedIndex
            if (index < 0) {
                lastSelectedRuntime = null
                return@addActionListener
            }
            val selectedRuntime = runtime.getItemAt(index)
            if (selectedRuntime == lastSelectedRuntime) return@addActionListener
            lastSelectedRuntime = selectedRuntime
            handlerPanel.setRuntime(selectedRuntime.toSdkRuntime())
            architectureModel.replaceAll(selectedRuntime.architectures?.toMutableList() ?: mutableListOf(LambdaArchitecture.DEFAULT))
            architecture.isEnabled = architectureModel.size > 1
        }
        val supportedRuntimes = LambdaBuilder.supportedRuntimeGroups().flatMap { it.supportedRuntimes }.sorted()
        runtimeModel.setAll(supportedRuntimes)
        runtimeModel.selectedItem = RuntimeGroup.determineRuntime(project)?.let { if (it in supportedRuntimes) it else null }
    }
}


// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package software.aws.toolkits.jetbrains.services.lambda.execution.local

import com.intellij.execution.util.PathMappingsComponent
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.SimpleListCellRenderer
import com.intellij.ui.SortedComboBoxModel
import com.intellij.util.PathMappingSettings
import org.jetbrains.yaml.YAMLFileType
import software.amazon.awssdk.services.lambda.model.PackageType
import software.aws.toolkits.core.utils.getLogger
import software.aws.toolkits.core.utils.warn
import software.aws.toolkits.jetbrains.services.cloudformation.Function
import software.aws.toolkits.jetbrains.services.cloudformation.SamFunction
import software.aws.toolkits.jetbrains.services.lambda.execution.sam.ImageDebugSupport
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils
import software.aws.toolkits.jetbrains.services.lambda.sam.SamTemplateUtils.getFunctionEnvironmentVariables
import software.aws.toolkits.jetbrains.ui.KeyValueTextField
import software.aws.toolkits.jetbrains.ui.installTextFieldProjectFileBrowseListener
import software.aws.toolkits.jetbrains.utils.ui.find
import software.aws.toolkits.jetbrains.utils.ui.selected
import java.io.File
import java.util.Comparator
import javax.swing.DefaultComboBoxModel
import javax.swing.JComboBox
import javax.swing.JPanel
import javax.swing.event.DocumentEvent

class TemplateSettings(val project: Project) {
    lateinit var panel: JPanel
        private set
    lateinit var templateFile: TextFieldWithBrowseButton
        private set
    lateinit var function: JComboBox<Function>
        private set
    lateinit var pathMappingsTable: PathMappingsComponent
        private set
    private lateinit var functionModels: DefaultComboBoxModel<Function>
    private lateinit var imageSettingsPanel: JPanel
    lateinit var environmentVariables: KeyValueTextField
        private set
    lateinit var imageDebugger: JComboBox<ImageDebugSupport>
        private set
    lateinit var imageDebuggerModel: SortedComboBoxModel<ImageDebugSupport>
        private set

    val isImage
        get() = function.selected()?.packageType() == PackageType.IMAGE

    init {
        // by default, do not show the image settings or path mappings table
        imageSettingsPanel.isVisible = false
        pathMappingsTable.isVisible = false
        environmentVariables.isEnabled = false
        installTextFieldProjectFileBrowseListener(
            project,
            templateFile,
            FileChooserDescriptorFactory.createSingleFileDescriptor(YAMLFileType.YML)
        ) {
            it.canonicalPath ?: ""
        }

        templateFile.textField.document.addDocumentListener(object : DocumentAdapter() {
            override fun textChanged(e: DocumentEvent) {
                updateFunctionModel(templateFile.text)
            }
        })
        function.addActionListener {
            val selected = function.selected()
            imageSettingsPanel.isVisible = selected is SamFunction && selected.packageType() == PackageType.IMAGE
            if (selected == null) {
                environmentVariables.isEnabled = false
            } else {
                environmentVariables.isEnabled = true
                setEnvVars(selected)
            }
        }
        imageDebugger.addActionListener {
            val pathMappingsApplicable = pathMappingsApplicable()
            pathMappingsTable.isVisible = pathMappingsApplicable
            if (!pathMappingsApplicable) {
                // Clear mappings if it's no longer applicable
                pathMappingsTable.setMappingSettings(PathMappingSettings())
            }
        }
        imageDebuggerModel.setAll(ImageDebugSupport.debuggers().values)
    }

    private fun createUIComponents() {
        functionModels = DefaultComboBoxModel()
        function = ComboBox(functionModels)
        environmentVariables = KeyValueTextField()
        imageDebuggerModel = SortedComboBoxModel(compareBy(Comparator.naturalOrder()) { it.displayName() })
        imageDebugger = ComboBox(imageDebuggerModel)
        imageDebugger.renderer = SimpleListCellRenderer.create { label, value, _ -> label.text = value?.displayName() }
    }

    fun setTemplateFile(path: String?) {
        templateFile.text = path ?: ""
        updateFunctionModel(path)
    }

    private fun updateFunctionModel(path: String?) {
        if (path.isNullOrBlank()) {
            templateFile.text = ""
            updateFunctionModel(emptyList())
            return
        }
        val file = File(path)
        if (!file.exists() || !file.isFile) {
            updateFunctionModel(emptyList())
        } else {
            val functions = SamTemplateUtils.findFunctionsFromTemplate(project, file)
            updateFunctionModel(functions)
        }
    }

    fun selectFunction(logicalFunctionName: String?) {
        val function = functionModels.find { it.logicalName == logicalFunctionName } ?: return
        functionModels.selectedItem = function
    }

    private fun setEnvVars(function: Function) = try {
        environmentVariables.envVars = getFunctionEnvironmentVariables(File(templateFile.text).toPath(), function.logicalName)
    } catch (e: Exception) {
        // We don't want to throw exceptions out to the UI when we fail to parse the template, so log and continue
        LOG.warn(e) { "Failed to set environment variables field" }
    }

    private fun updateFunctionModel(functions: List<Function>) {
        functionModels.removeAllElements()
        function.isEnabled = functions.isNotEmpty()
        functionModels.addAll(functions)
        if (functions.size == 1) {
            functionModels.setSelectedItem(functions[0])
        } else {
            function.setSelectedIndex(-1)
        }
    }

    private fun pathMappingsApplicable(): Boolean = imageDebugger.selected()?.supportsPathMappings() ?: false

    private companion object {
        val LOG = getLogger<TemplateSettings>()
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.remote

import com.intellij.execution.Location
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import software.aws.toolkits.jetbrains.core.DummyPsiElement
import software.aws.toolkits.jetbrains.services.lambda.LambdaFunction

/**
 * Custom [Location] to represent the Lambda elements in the explorer so the Remote rune configurations work. A fake
 * PSI element is also used since no valid PSI can exist to represent a remote Lambda
 */
class RemoteLambdaLocation(private val project: Project, val lambdaFunction: LambdaFunction) : Location<DummyPsiElement>() {
    private val element = DummyPsiElement(project)

    override fun getProject(): Project = project

    override fun getModule(): Module? = null

    override fun <T : PsiElement?> getAncestors(
        ancestorClass: Class<T>?,
        strict: Boolean
    ): MutableIterator<Location<T>> = mutableListOf<Location<T>>().iterator()

    override fun getPsiElement(): DummyPsiElement = element
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.lambda.execution.remote

import com.intellij.util.xmlb.annotations.Property
import software.aws.toolkits.jetbrains.services.lambda.execution.BaseLambdaOptions

class RemoteLambdaOptions : BaseLambdaOptions() {
    @get:Property(flat = true) // flat for backwards compat
    var functionOptions = FunctionOptions()
}

class FunctionOptions {
    var functionName: String? = null
}