package org.jetbrains.dokka

import org.jetbrains.jet.lang.resolve.*
import org.jetbrains.jet.lang.descriptors.*
import org.jetbrains.jet.lang.descriptors.impl.*
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.types.JetType

class DocumentationNodeBuilder(val context: BindingContext) : DeclarationDescriptorVisitorEmptyBodies<DocumentationNode, DocumentationNode>() {

    fun reference(from: DocumentationNode, to: DocumentationNode, kind: DocumentationReference.Kind) {
        from.addReferenceTo(to, kind)
        if (kind == DocumentationReference.Kind.Link)
            to.addReferenceTo(from, DocumentationReference.Kind.Link)
        else
            to.addReferenceTo(from, DocumentationReference.Kind.Owner)
    }

    fun addModality(descriptor: MemberDescriptor, data: DocumentationNode) {
        val modifier = descriptor.getModality().name().toLowerCase()
        val node = DocumentationNode(descriptor, modifier, DocumentationContent.Empty, DocumentationNode.Kind.Modifier)
        reference(data, node, DocumentationReference.Kind.Detail)
    }

    fun addVisibility(descriptor: MemberDescriptor, data: DocumentationNode) {
        val modifier = descriptor.getVisibility().toString()
        val node = DocumentationNode(descriptor, modifier, DocumentationContent.Empty, DocumentationNode.Kind.Modifier)
        reference(data, node, DocumentationReference.Kind.Detail)
    }

    fun addType(descriptor: DeclarationDescriptor, t: JetType?, data: DocumentationNode) {
        if (t == null)
            return
        val typeConstructor = t.getConstructor()
        val classifierDescriptor = typeConstructor.getDeclarationDescriptor()
        val name = when (classifierDescriptor) {
            is Named -> classifierDescriptor.getName().asString()
            else -> "<BAD>"
        }
        val node = DocumentationNode(descriptor, name, DocumentationContent.Empty, DocumentationNode.Kind.Type)
        reference(data, node, DocumentationReference.Kind.Detail)

        for (param in t.getArguments())
            addType(descriptor, param.getType(), node)
    }

    override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, DocumentationNode.Kind.Unknown)
        reference(data!!, node, DocumentationReference.Kind.Link)
        return node
    }

    override fun visitReceiverParameterDescriptor(descriptor: ReceiverParameterDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val node = DocumentationNode(descriptor!!, descriptor.getName().asString(), DocumentationContent.Empty, DocumentationNode.Kind.Receiver)
        reference(data!!, node, DocumentationReference.Kind.Detail)

        addType(descriptor, descriptor.getType(), node)

        return node
    }

    override fun visitValueParameterDescriptor(descriptor: ValueParameterDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, DocumentationNode.Kind.Parameter)
        reference(data!!, node, DocumentationReference.Kind.Detail)

        addType(descriptor, descriptor.getType(), node)

        return node
    }

    override fun visitClassDescriptor(descriptor: ClassDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, when (descriptor.getKind()) {
            ClassKind.OBJECT -> DocumentationNode.Kind.Object
            ClassKind.CLASS_OBJECT -> DocumentationNode.Kind.Object
            ClassKind.TRAIT -> DocumentationNode.Kind.Interface
            ClassKind.ENUM_CLASS -> DocumentationNode.Kind.Enum
            ClassKind.ENUM_ENTRY -> DocumentationNode.Kind.EnumItem
            else -> DocumentationNode.Kind.Class
        })
        reference(data!!, node, DocumentationReference.Kind.Member)
        addModality(descriptor, node)
        addVisibility(descriptor, node)
        return node
    }

    override fun visitFunctionDescriptor(descriptor: FunctionDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, DocumentationNode.Kind.Function)
        reference(data!!, node, DocumentationReference.Kind.Member)

        addType(descriptor, descriptor.getReturnType(), node)
        addModality(descriptor, node)
        addVisibility(descriptor, node)

        return node
    }

    override fun visitTypeParameterDescriptor(descriptor: TypeParameterDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, DocumentationNode.Kind.TypeParameter)
        reference(data!!, node, DocumentationReference.Kind.Detail)
        val builtIns = KotlinBuiltIns.getInstance()
        for (constraint in descriptor.getUpperBounds()) {
            if (constraint == builtIns.getDefaultBound())
                continue
            val constraintNode = DocumentationNode(descriptor, constraint.toString(), DocumentationContent.Empty, DocumentationNode.Kind.UpperBound)
            reference(node, constraintNode, DocumentationReference.Kind.Detail)
        }
        for (constraint in descriptor.getLowerBounds()) {
            if (builtIns.isNothing(constraint))
                continue
            val constraintNode = DocumentationNode(descriptor, constraint.toString(), DocumentationContent.Empty, DocumentationNode.Kind.LowerBound)
            reference(node, constraintNode, DocumentationReference.Kind.Detail)
        }
        return node
    }

    override fun visitPropertyDescriptor(descriptor: PropertyDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, DocumentationNode.Kind.Property)
        reference(data!!, node, DocumentationReference.Kind.Member)

        addType(descriptor, descriptor.getType(), node)
        addModality(descriptor, node)
        addVisibility(descriptor, node)
        return node
    }

    override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val doc = context.getDocumentation(descriptor!!)
        val node = DocumentationNode(descriptor, descriptor.getName().asString(), doc, DocumentationNode.Kind.Constructor)
        reference(data!!, node, DocumentationReference.Kind.Member)

        addVisibility(descriptor, node)

        return node
    }

    override fun visitPackageViewDescriptor(descriptor: PackageViewDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val node = DocumentationNode(descriptor!!, descriptor.getFqName().asString(), DocumentationContent.Empty, DocumentationNode.Kind.Package)
        reference(data!!, node, DocumentationReference.Kind.Member)
        return node
    }

    override fun visitPackageFragmentDescriptor(descriptor: PackageFragmentDescriptor?, data: DocumentationNode?): DocumentationNode? {
        val node = DocumentationNode(descriptor!!, descriptor.fqName.asString(), DocumentationContent.Empty, DocumentationNode.Kind.Package)
        reference(data!!, node, DocumentationReference.Kind.Member)
        return node
    }
}