/**
 * @author Fedor Isakov
 */

import jetbrains.mps.logic.reactor.core.RulesDiff
import jetbrains.mps.logic.reactor.evaluation.InvocationContext
import jetbrains.mps.logic.reactor.evaluation.Supervisor
import jetbrains.mps.logic.reactor.logical.LogicalContext
import jetbrains.mps.logic.reactor.logical.MetaLogical
import jetbrains.mps.logic.reactor.program.*
import java.util.*
import java.util.Collections.*
import kotlin.collections.LinkedHashMap

class ProgramBuilder(val registry: MockConstraintRegistry)  {

    fun program(name: String, rulesLists: List<RulesList>): Program {
        for (h in rulesLists) {
            for (r in h.rules()) {
                registry.update(r)
            }
        }
        return MockProgram(name, rulesLists, registry)
    }
    
}

class HandlerBuilder(val name: String) {

    val rules = LinkedHashMap<Any, Rule>()

    constructor(name: String, rulesList: RulesList) : this(name) {
        for (r in rulesList.rules()) {
            appendRule(r)
        }
    }

    fun appendRule(rule: Rule) {
        rules[rule.uniqueTag()] = rule
    }

    fun toHandler(): RulesList = MockHandler(name, rules.values.toList())
}

class RuleBuilder(val tag: String, val segmentPath: List<Any>) {
    val kept = ArrayList<Constraint>()
    val replaced = ArrayList<Constraint>()
    val guard = ArrayList<Predicate>()
    val body = ArrayList<ArrayList<AndItem>>()

    fun appendHeadKept(vararg cst: Constraint) {
        kept.addAll(cst)
    }
    fun appendHeadReplaced(vararg cst: Constraint) {
        replaced.addAll(cst)
    }
    fun appendGuard(vararg prd: Predicate) {
        guard.addAll(prd)
    }
    fun appendBody(alt: Boolean, vararg andItem: AndItem) {
        if (alt || body.isEmpty()) body.add(ArrayList<AndItem>())
        body.last().addAll(andItem)
    }
    fun toRule(): Rule = MockRule(Rule.Tag(tag), segmentPath, kept, replaced, guard, body)
}

class MockHandler(
    val name: String,
    val rules: List<Rule>) : RulesList() {

    override fun name(): String = name

    override fun rules(): Iterable<Rule> = rules
}

class MockRule(
    val tag: Rule.Tag,
    val segmentPath: List<Any>,
    val kept: Collection<Constraint>,
    val replaced: Collection<Constraint>,
    val guard: Collection<Predicate>,
    val body: Collection<Collection<AndItem>>) : Rule() {

    override fun kind(): Kind = TODO()

    override fun segmentPath(): List<Any> = segmentPath

    override fun uniqueTag() = tag

    override fun headKept(): Iterable<Constraint> = kept

    override fun headReplaced(): Iterable<Constraint> = replaced

    override fun guard(): Iterable<Predicate> = guard

    override fun bodyAlternation(): Iterable<Iterable<AndItem>> = body

    override fun all(): Iterable<AndItem> =
        if (body.isEmpty()) throw InvalidRuleException("no body")
        else (kept + replaced).map { it as AndItem } + guard + body.flatten()
}

class MockProgram(val name: String, val rulesLists: List<RulesList>, val registry: MockConstraintRegistry) : Program() {
    private var rulesDiff: RulesDiff = RulesDiff.emptyDiff()

    override fun preambleInfo(): PreambleInfo = PreambleInfo.EMPTY

    override fun incrementalDiff(): RulesDiff = rulesDiff

    override fun withRulesDiff(diff: RulesDiff): MockProgram {
        this.rulesDiff = diff
        return this
    }

    override fun name(): String = name

    override fun rulesLists(): Iterable<RulesList> = unmodifiableCollection(rulesLists)

    override fun rules(): MutableIterable<Rule> = unmodifiableCollection(rulesLists.flatMap { it.rules() })
}


open class MockSupervisor : Supervisor {

    override fun instantiateArguments(arguments: List<*>, logicalContext: LogicalContext, invocationContext: InvocationContext): List<*> =
        arguments.map { a ->
            if (a is MetaLogical<*>) logicalContext.variable(a)
            else a
        }

//    override fun handleFeedback(rule: Rule?, feedback: EvaluationFeedback?): Boolean = false
}

class MockConstraintRegistry() {

    private val myConstraintArgTypes = HashMap<ConstraintSymbol, List<Class<*>>>().withDefault { Collections.emptyList() }

    private val myPredicateSymbols = HashSet<PredicateSymbol>()

    fun update(rule: Rule) {
        for(item in rule.all()) {
            checkValid(item)
            introduce(item)
        }
    }

    fun checkValid(item: AndItem) {
        if (item.symbol().arity() != item.arguments().size) {
            throw InvalidConstraintException("arity mismatch: ${item.symbol()}")
        }
        for (arg in item.arguments()) {
            if (arg == null) {
                throw InvalidConstraintException("argument is null: ${item.symbol()}")
            }
        }
        when(item) {
            is Constraint -> {
                if (myConstraintArgTypes.containsKey(item.symbol())) {
                    if (!item.argumentTypes().equals(myConstraintArgTypes.get(item.symbol()))) {
                        throw InvalidConstraintException("argument types mismatch: ${item.symbol()}")
                    }
                }
            }
            is Predicate ->  {
                // nothing
            }
            else          -> throw InvalidConstraintException("unknown item: ${item}")
        }
    }

    fun introduce(item: AndItem) {
        when(item) {
            is Constraint -> myConstraintArgTypes.put(item.symbol(), item.argumentTypes())
            is Predicate -> myPredicateSymbols.add(item.symbol())
            else          -> throw InvalidConstraintException("unknown item: ${item}")
        }
    }

    fun constraintSymbols(): Iterable<ConstraintSymbol> =
            unmodifiableSet(myConstraintArgTypes.keys)

    fun constraintArgTypes(symbol: ConstraintSymbol): List<Class<*>> =
            unmodifiableList(myConstraintArgTypes.getOrPut(symbol, { emptyList() }))

    fun predicateSymbols(): Iterable<PredicateSymbol> =
            unmodifiableSet(myPredicateSymbols)

}

