/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.gradle.kotlin.dsl.accessors.runtime

import org.gradle.api.Action
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.internal.DynamicObjectAware
import org.gradle.api.internal.plugins.BuildModel
import org.gradle.api.internal.plugins.Definition
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderConvertible
import org.gradle.kotlin.dsl.invoke
import org.gradle.kotlin.dsl.newInstance
import org.gradle.kotlin.dsl.support.mapOfNonNullValuesOf
import org.gradle.kotlin.dsl.support.uncheckedCast
import org.gradle.plugin.software.internal.ProjectFeatureImplementation
import org.gradle.plugin.software.internal.ProjectFeatureSupportInternal

@Suppress("unused") // invoked from generated bytecode
fun extensionOf(target: Any, extensionName: String): Any =
    (target as ExtensionAware).extensions.getByName(extensionName)

@Suppress("unused") // invoked from generated bytecode
fun <T : Dependency> addDependencyTo(
    dependencies: DependencyHandler,
    configuration: String,
    dependencyNotation: Any,
    configurationAction: Action<T>
): T = dependencies.run {
    uncheckedCast<T>(create(dependencyNotation)).also { dependency ->
        configurationAction.execute(dependency)
        add(configuration, dependency)
    }
}

@Suppress("unused") // invoked from generated bytecode
fun addConfiguredDependencyTo(
    dependencies: DependencyHandler,
    configuration: String,
    dependencyNotation: Provider<*>,
    configurationAction: Action<ExternalModuleDependency>
) {
    dependencies.addProvider(configuration, dependencyNotation, configurationAction)
}

@Suppress("unused") // invoked from generated bytecode
fun addConfiguredDependencyTo(
    dependencies: DependencyHandler,
    configuration: String,
    dependencyNotation: ProviderConvertible<*>,
    configurationAction: Action<ExternalModuleDependency>
) {
    dependencies.addProviderConvertible(configuration, dependencyNotation, configurationAction)
}


@Suppress("LongParameterList", "DEPRECATION")
@Deprecated("Use single-string notation instead")
fun addExternalModuleDependencyTo(
    dependencyHandler: DependencyHandler,
    targetConfiguration: String,
    group: String,
    name: String,
    version: String?,
    configuration: String?,
    classifier: String?,
    ext: String?,
    action: Action<ExternalModuleDependency>?
): ExternalModuleDependency = externalModuleDependencyFor(
    dependencyHandler,
    group,
    name,
    version,
    configuration,
    classifier,
    ext
).also {
    action?.execute(it)
    dependencyHandler.add(targetConfiguration, it)
}

@Deprecated("Use single-string notation instead")
fun externalModuleDependencyFor(
    dependencyHandler: DependencyHandler,
    group: String,
    name: String,
    version: String?,
    configuration: String?,
    classifier: String?,
    ext: String?
): ExternalModuleDependency = dependencyHandler.create(
    mapOfNonNullValuesOf(
        "group" to group,
        "name" to name,
        "version" to version,
        "configuration" to configuration,
        "classifier" to classifier,
        "ext" to ext
    )
) as ExternalModuleDependency


@Suppress("unused") // invoked from generated bytecode
fun <T : Any> maybeRegister(
    container: NamedDomainObjectContainer<T>,
    name: String,
    configure: Action<in T>
) {
    with(container) {
        if (name in names) {
            named(name, configure)
        } else {
            register(name, configure)
        }
    }
}


@Suppress("unused") // invoked from generated bytecode
fun applyProjectFeature(
    target: DynamicObjectAware,
    name: String,
    configure: Action<in Any>
) {
    val context = when (target) {
        is ProjectInternal ->
            target.objects.newInstance<ProjectFeatureSupportInternal.DefaultProjectFeatureDefinitionContext.Factory>()
                .create(target)

        is Definition<*> -> ProjectFeatureSupportInternal.getContext(target)
        else -> throw IllegalArgumentException("Unexpected definition object $target, cannot get context to apply the feature")
    }
    val projectFeatures = context.projectFeatureRegistry.projectFeatureImplementations.getValue(name)
    context.projectFeatureApplicator.applyFeatureTo(target, projectFeatures)
    configure.invoke(getProjectFeatureDefinitionInstance(projectFeatures, target))
}

@Suppress("UNCHECKED_CAST")
private fun <T: Definition<V>, V: BuildModel> getProjectFeatureDefinitionInstance(projectFeature: ProjectFeatureImplementation<T, V>, receiverObject: DynamicObjectAware): T =
    (ProjectFeatureSupportInternal.getContext(receiverObject).childrenDefinitions()[projectFeature]
        ?: error("No definition found for project feature ${projectFeature.featureName} in ${receiverObject::class.qualifiedName}")) as T


@Suppress("unused") // invoked from generated bytecode
fun functionToAction(f: (Any?) -> Unit): Action<Any> =
    Action { f(this) }
