/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * 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 com.android.internal.systemui.lint

import com.android.ide.common.blame.SourcePosition
import com.android.tools.lint.client.api.UElementHandler
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Implementation
import com.android.tools.lint.detector.api.Issue
import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Location
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
import java.time.Year
import org.jetbrains.uast.UComment
import org.jetbrains.uast.UFile

/**
 * Checks if every AOSP Java/Kotlin source code file is starting with Apache license information.
 */
class MissingApacheLicenseDetector : Detector(), SourceCodeScanner {

    override fun getApplicableUastTypes() = listOf(UFile::class.java)

    override fun createUastHandler(context: JavaContext): UElementHandler? {
        return object : UElementHandler() {
            override fun visitFile(node: UFile) {
                val firstComment = node.allCommentsInFile.firstOrNull()
                // Normally we don't need to explicitly handle suppressing case and just return
                // error as usual with indicating node and lint will ignore it for us. But here
                // suppressing will be applied on top of comment that doesn't exist so we don't have
                // node to return - it's a bit of corner case
                if (firstComment != null && firstComment.isSuppressingComment()) {
                    return
                }
                if (firstComment == null || !firstComment.isLicenseComment()) {
                    val firstLineOfFile =
                        Location.create(
                            context.file,
                            SourcePosition(/* lineNumber= */ 1, /* column= */ 1, /* offset= */ 0)
                        )
                    context.report(
                        issue = ISSUE,
                        location = firstLineOfFile,
                        message =
                            "License header is missing\n" +
                                "Please add the following copyright and license header to the" +
                                " beginning of the file:\n\n" +
                                copyrightHeader
                    )
                }
            }
        }
    }

    private fun UComment.isSuppressingComment(): Boolean {
        val suppressingComment =
            "//noinspection ${MissingApacheLicenseDetector::class.java.simpleName}"
        return text.contains(suppressingComment)
    }

    private fun UComment.isLicenseComment(): Boolean {
        // We probably don't want to compare full copyright header in case there are some small
        // discrepancies in already existing files, e.g. year. We could do regexp but it should be
        // good enough if this detector deals with missing
        // license header instead of incorrect license header
        return text.contains("Apache License")
    }

    private val copyrightHeader: String
        get() =
            """
            /*
             * Copyright (C) ${Year.now().value} The Android Open Source Project
             *
             * 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.
             */
            """
                .trimIndent()
                .trim()

    companion object {
        @JvmField
        val ISSUE: Issue =
            Issue.create(
                id = "MissingApacheLicenseDetector",
                briefDescription = "File is missing Apache license information",
                explanation =
                    """
                        Every source code file should have copyright and license information \
                        attached at the beginning.""",
                category = Category.COMPLIANCE,
                priority = 8,
                // ignored by default and then explicitly overridden in SysUI's soong configuration
                severity = Severity.IGNORE,
                implementation =
                    Implementation(MissingApacheLicenseDetector::class.java, Scope.JAVA_FILE_SCOPE),
            )
    }
}
