/*
 * Copyright 2020 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 gradlebuild.basics

import org.gradle.api.Describable
import org.gradle.api.provider.Property
import org.gradle.api.provider.ValueSource
import org.gradle.api.provider.ValueSourceParameters
import org.gradle.api.tasks.Optional
import java.text.SimpleDateFormat
import java.util.Date
import java.util.TimeZone


/**
 * Computes the timestamp that identifies the distribution built by this build.
 *
 * On the [Date] paths below the result is *not* stable across invocations of [obtain], so this
 * value source must be obtained exactly once per build and the result shared by every project.
 * See [BuildEnvironmentService.buildTimestamp], which is the only place that may create it.
 */
abstract class BuildTimestampValueSource : ValueSource<String, BuildTimestampValueSource.Parameters>, Describable {

    interface Parameters : ValueSourceParameters {

        @get:Optional
        val buildTimestampFromBuildReceipt: Property<String>

        @get:Optional
        val buildTimestampFromGradleProperty: Property<String>

        @get:Optional
        val enableConfigurationCacheForDocsTests: Property<Boolean>

        val runningOnCi: Property<Boolean>

        val runningInstallTask: Property<Boolean>
        val runningDocsTestTask: Property<Boolean>
    }

    override fun obtain(): String? = parameters.run {
        if (enableConfigurationCacheForDocsTests.getOrElse(false)) {
            // If the CC is enabled for docs tests, use a static dummy timestamp (the Epoch) so that we get hits,
            // otherwise we'll use the current timestamp by default and miss
            val formatter = SimpleDateFormat("yyyyMMddHHmmssZ").apply {
                timeZone = TimeZone.getTimeZone("UTC")
            }
            return formatter.format(Date(0L))
        }

        val buildTimestampFromReceipt = buildTimestampFromBuildReceipt.orNull
        if (buildTimestampFromReceipt != null) {
            println("Using timestamp from incoming build receipt: $buildTimestampFromReceipt")
            return buildTimestampFromReceipt
        }

        val timestampFormat = SimpleDateFormat("yyyyMMddHHmmssZ").apply {
            timeZone = TimeZone.getTimeZone("UTC")
        }
        val buildTimestampFromProperty = buildTimestampFromGradleProperty.orNull
        val buildTime = when {
            buildTimestampFromProperty != null -> {
                timestampFormat.parse(buildTimestampFromProperty)
            }
            runningInstallTask.get() || runningDocsTestTask.get() || runningOnCi.get() -> {
                Date()
            }
            else -> {
                Date().withoutTime()
            }
        }
        return timestampFormat.format(buildTime)
    }

    override fun getDisplayName(): String =
        "the build timestamp ($timestampSource)"

    private
    val timestampSource: String
        get() = parameters.run {
            when {
                buildTimestampFromBuildReceipt.isPresent -> "from build receipt"
                buildTimestampFromGradleProperty.isPresent -> "from buildTimestamp property"
                runningInstallTask.get() -> "from current time because installing"
                runningDocsTestTask.get() -> "from current time because testing docs"
                runningOnCi.get() -> "from current time because CI"
                else -> "from current date"
            }
        }

    private
    fun Date.withoutTime(): Date = SimpleDateFormat("yyyy-MM-dd").run {
        parse(format(this@withoutTime))
    }
}
