def excludes = [
        // DI graph
        '**/*Module.*',
        '**/*Module*.*',

        // Android composables
        '**/*Screen*',
        '**/components/*',
        '**/*Compose*.*',

        // Android framework
        '**/*Activity*',
        '**/*AndroidService*',
        '**/*Application*',

        // Generated
        '**/*serializer*',
        '**/*Serializer*',
        "**/*request/*Companion*.*",
        '**/*QueriesImpl*',
        '**/*Db*',
        '**/Select*',

        // Tmp until serializationx can ignore generated
        '**/Api*',
        '**/RoomEvent*',
]

def initializeReport(report, projects, classExcludes) {
    projects.each { project -> project.apply plugin: 'jacoco' }
    report.executionData { fileTree(rootProject.rootDir.absolutePath).include("**/build/**/*.exec") }

    report.reports {
        xml.enabled true
        html.enabled true
        csv.enabled false
    }

    gradle.projectsEvaluated {
        def androidSourceDirs = []
        def androidClassDirs = []

        projects.each { project ->
            switch (project) {
                case { project.plugins.hasPlugin("com.android.application") }:
                    androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/debug")
                    androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
                    break
                case { project.plugins.hasPlugin("com.android.library") }:
                    androidClassDirs.add("${project.buildDir}/tmp/kotlin-classes/release")
                    androidSourceDirs.add("${project.projectDir}/src/main/kotlin")
                    break
                default:
                    report.sourceSets project.sourceSets.main
            }
        }

        report.sourceDirectories.setFrom(report.sourceDirectories + files(androidSourceDirs))
        def classFiles = androidClassDirs.collect { files(it).files }.flatten()
        report.classDirectories.setFrom(files((report.classDirectories.files + classFiles).collect {
            fileTree(dir: it, excludes: classExcludes)
        }))
    }
}

def collectProjects(predicate) {
    return subprojects.findAll { it.buildFile.isFile() && predicate(it) }
}

task allCodeCoverageReport(type: JacocoReport) {
    outputs.upToDateWhen { false }
    rootProject.apply plugin: 'jacoco'
    def projects = collectProjects { !it.name.contains("stub") && !it.name.contains("-noop") }
    dependsOn { ["app:assembleDebug"] + projects*.test }
    initializeReport(it, projects, excludes)
}

task unitTestCodeCoverageReport(type: JacocoReport) {
    outputs.upToDateWhen { false }
    rootProject.apply plugin: 'jacoco'
    def projects = collectProjects { !it.name.contains("test-harness") && !it.name.contains("stub") && !it.name.contains("-noop") }
    dependsOn { ["app:assembleDebug"] + projects*.test }
    initializeReport(it, projects, excludes)
}