/*
 * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.kapt.base.incremental

import java.io.File
import java.io.Serializable

class IncrementalAptCache : Serializable {

    private val aggregatingGenerated: MutableSet<File> = mutableSetOf()
    private val aggregatedTypes: MutableSet<String> = linkedSetOf()
    private val isolatingMapping: MutableMap<File, String> = mutableMapOf()

    var isIncremental = true
        private set

    fun updateCache(processors: List<IncrementalProcessor>, failedToAnalyzeSources: Boolean): Boolean {
        if (failedToAnalyzeSources) {
            invalidateCache()
            return false
        }

        val aggregating = mutableListOf<IncrementalProcessor>()
        val isolating = mutableListOf<IncrementalProcessor>()
        val nonIncremental = mutableListOf<IncrementalProcessor>()
        processors.forEach {
            when (it.getRuntimeType()) {
                RuntimeProcType.AGGREGATING -> aggregating.add(it)
                RuntimeProcType.ISOLATING -> isolating.add(it)
                RuntimeProcType.NON_INCREMENTAL -> nonIncremental.add(it)
            }
        }

        if (nonIncremental.isNotEmpty()) {
            invalidateCache()
            return false
        }

        aggregatingGenerated.clear()
        aggregating.forEach {
            aggregatingGenerated.addAll(it.getGeneratedToSources().keys)
        }

        aggregatedTypes.clear()
        aggregatedTypes.addAll(aggregating.flatMap { it.getAggregatedTypes() })

        isolating.forEach {
            it.getGeneratedToSources().forEach { (file, type) ->
                isolatingMapping[file] = type!!
            }
        }

        return true
    }

    /**
     * Invalidates all data collected about aggregating APs, making the cache ready for the next round of data collection. Also,
     * all files generated by aggregating APs are deleted.
     */
    fun invalidateAggregating() {
        aggregatingGenerated.forEach { it.delete() }
        aggregatingGenerated.clear()
        aggregatedTypes.clear()
    }

    /**
     * Prepares isolating processors for incremental compilation. The specified generated files are removed, and mapping
     * information is deleted for them. The invalidation is non-transitive.
     */
    fun invalidateIsolatingForOriginTypes(originatingTypes: Set<String>) {
        val isolatingGenerated = mutableSetOf<File>()
        isolatingMapping.forEach { (file, type) ->
            if (type in originatingTypes) {
                isolatingGenerated.add(file)
            }
        }

        isolatingGenerated.forEach {
            isolatingMapping.remove(it)
            it.delete()
        }
    }

    fun getIsolatingGeneratedTypesForOrigins(
        originatingTypes: Set<String>,
        typeInfoProvider: (Collection<File>) -> Set<String>
    ): List<String> {
        val allGeneratedTypes = mutableListOf<String>()
        var currentOrigins = originatingTypes.toSet()

        // We need to do it in a loop because mapping could be: [AGenerated.java -> A.java, AGeneratedGenerated.java -> AGenerated.java]
        while (currentOrigins.isNotEmpty()) {
            val generated = mutableSetOf<File>()
            isolatingMapping.forEach { (file, origin) ->
                if (origin in currentOrigins) {
                    generated.add(file)
                }
            }
            currentOrigins = typeInfoProvider(generated)
            allGeneratedTypes.addAll(currentOrigins)
        }
        return allGeneratedTypes
    }

    private fun File.isJavaFileOrClass() = extension == "java" || extension == "class"

    private fun invalidateCache() {
        isIncremental = false
        aggregatingGenerated.clear()
        isolatingMapping.clear()
        aggregatedTypes.clear()
    }

    /** Gets the originating type for the specified type generated by isolating AP. */
    fun getOriginForGeneratedIsolatingType(generatedType: String, sourceFileProvider: (String) -> File): String {
        val generatedFile = sourceFileProvider(generatedType)
        return isolatingMapping.getValue(generatedFile)
    }

    /** Returns types that were processed by aggregating APs. */
    fun getAggregatingOrigins(): Set<String> = aggregatedTypes

    /** Returns all types generated by aggregating APs. */
    fun getAggregatingGeneratedTypes(typeInfoProvider: (Collection<File>) -> Set<String>): Set<String> {
        val generatedAggregating: MutableSet<File> = HashSet(aggregatingGenerated.size)
        aggregatingGenerated.forEach {
            if (it.isJavaFileOrClass()) {
                generatedAggregating.add(it)
            }
        }
        return typeInfoProvider(generatedAggregating)
    }

    /** Returns all types generated by isolating APs. */
    fun getIsolatingGeneratedTypes(typeInfoProvider: (Collection<File>) -> Set<String>): Set<String> {
        val generatedIsolating: MutableSet<File> = HashSet(isolatingMapping.size)

        isolatingMapping.keys.forEach {
            if (it.isJavaFileOrClass()) {
                generatedIsolating.add(it)
            }
        }
        return typeInfoProvider(generatedIsolating)
    }
}