"""
    The Bro domain for Sphinx.
"""

def setup(Sphinx):
    Sphinx.add_domain(BroDomain)
    Sphinx.add_node(see)
    Sphinx.add_directive_to_domain('bro', 'see', SeeDirective)
    Sphinx.connect('doctree-resolved', process_see_nodes)

from sphinx import addnodes
from sphinx.domains import Domain, ObjType, Index
from sphinx.locale import l_, _
from sphinx.directives import ObjectDescription
from sphinx.roles import XRefRole
from sphinx.util.nodes import make_refnode
from sphinx import version_info

from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.parsers.rst import directives
from docutils.parsers.rst.roles import set_classes

class see(nodes.General, nodes.Element):
    refs = []

class SeeDirective(Directive):
    has_content = True

    def run(self):
        n = see('')
        n.refs = " ".join(self.content).split()
        return [n]

# Wrapper for creating a tuple for index nodes, staying backwards
# compatible to Sphinx < 1.4:
def make_index_tuple(indextype, indexentry, targetname, targetname2):
    if version_info >= (1, 4, 0, '', 0):
        return (indextype, indexentry, targetname, targetname2, None)
    else:
        return (indextype, indexentry, targetname, targetname2)

def process_see_nodes(app, doctree, fromdocname):
    for node in doctree.traverse(see):
        content = []
        para = nodes.paragraph()
        para += nodes.Text("See also:", "See also:")
        for name in node.refs:
            join_str = " "
            if name != node.refs[0]:
                join_str  = ", "
            link_txt = join_str + name;

            if name not in app.env.domaindata['bro']['idtypes']:
                # Just create the text and issue warning
                app.env.warn(fromdocname,
                             'unknown target for ".. bro:see:: %s"' % (name))
                para += nodes.Text(link_txt, link_txt)
            else:
                # Create a reference
                typ = app.env.domaindata['bro']['idtypes'][name]
                todocname = app.env.domaindata['bro']['objects'][(typ, name)]

                newnode = nodes.reference('', '')
                innernode = nodes.literal(_(name), _(name))
                newnode['refdocname'] = todocname
                newnode['refuri'] = app.builder.get_relative_uri(
                    fromdocname, todocname)
                newnode['refuri'] += '#' + typ + '-' + name
                newnode.append(innernode)
                para += nodes.Text(join_str, join_str)
                para += newnode

        content.append(para)
        node.replace_self(content)

class BroGeneric(ObjectDescription):
    def update_type_map(self, idname):
        if 'idtypes' not in self.env.domaindata['bro']:
            self.env.domaindata['bro']['idtypes'] = {}
        self.env.domaindata['bro']['idtypes'][idname] = self.objtype

    def add_target_and_index(self, name, sig, signode):
        targetname = self.objtype + '-' + name
        if targetname not in self.state.document.ids:
            signode['names'].append(targetname)
            signode['ids'].append(targetname)
            signode['first'] = (not self.names)
            self.state.document.note_explicit_target(signode)

            objects = self.env.domaindata['bro']['objects']
            key = (self.objtype, name)
            if ( key in objects and self.objtype != "id" and
                 self.objtype != "type" ):
                self.env.warn(self.env.docname,
                              'duplicate description of %s %s, ' %
                              (self.objtype, name) +
                              'other instance in ' +
                              self.env.doc2path(objects[key]),
                              self.lineno)
            objects[key] = self.env.docname
            self.update_type_map(name)

        indextext = self.get_index_text(self.objtype, name)
        if indextext:
            self.indexnode['entries'].append(make_index_tuple('single',
                                             indextext, targetname,
                                             targetname))

    def get_index_text(self, objectname, name):
        return _('%s (%s)') % (name, self.objtype)

    def handle_signature(self, sig, signode):
        signode += addnodes.desc_name("", sig)
        return sig

class BroNamespace(BroGeneric):
    def add_target_and_index(self, name, sig, signode):
        targetname = self.objtype + '-' + name
        if targetname not in self.state.document.ids:
            signode['names'].append(targetname)
            signode['ids'].append(targetname)
            signode['first'] = (not self.names)
            self.state.document.note_explicit_target(signode)

            objects = self.env.domaindata['bro']['objects']
            key = (self.objtype, name)
            objects[key] = self.env.docname
            self.update_type_map(name)

        indextext = self.get_index_text(self.objtype, name)
        self.indexnode['entries'].append(make_index_tuple('single', indextext,
                                          targetname, targetname))
        self.indexnode['entries'].append(make_index_tuple('single',
                                          "namespaces; %s" % (sig),
                                          targetname, targetname))

    def get_index_text(self, objectname, name):
        return _('%s (namespace); %s') % (name, self.env.docname)

    def handle_signature(self, sig, signode):
        signode += addnodes.desc_name("", sig)
        return sig

class BroEnum(BroGeneric):
    def add_target_and_index(self, name, sig, signode):
        targetname = self.objtype + '-' + name
        if targetname not in self.state.document.ids:
            signode['names'].append(targetname)
            signode['ids'].append(targetname)
            signode['first'] = (not self.names)
            self.state.document.note_explicit_target(signode)

            objects = self.env.domaindata['bro']['objects']
            key = (self.objtype, name)
            objects[key] = self.env.docname
            self.update_type_map(name)

        indextext = self.get_index_text(self.objtype, name)
        #self.indexnode['entries'].append(make_index_tuple('single', indextext,
        #                                  targetname, targetname))
        m = sig.split()

        if len(m) < 2:
            self.env.warn(self.env.docname,
                          "bro:enum directive missing argument(s)")
            return

        if m[1] == "Notice::Type":
            if 'notices' not in self.env.domaindata['bro']:
                self.env.domaindata['bro']['notices'] = []
            self.env.domaindata['bro']['notices'].append(
                                (m[0], self.env.docname, targetname))
        self.indexnode['entries'].append(make_index_tuple('single',
                                          "%s (enum values); %s" % (m[1], m[0]),
                                          targetname, targetname))

    def handle_signature(self, sig, signode):
        m = sig.split()
        name = m[0]
        signode += addnodes.desc_name("", name)
        return name

class BroIdentifier(BroGeneric):
    def get_index_text(self, objectname, name):
        return name

class BroKeyword(BroGeneric):
    def get_index_text(self, objectname, name):
        return name

class BroAttribute(BroGeneric):
    def get_index_text(self, objectname, name):
        return _('%s (attribute)') % (name)

class BroNotices(Index):
    """
    Index subclass to provide the Bro notices index.
    """

    name = 'noticeindex'
    localname = l_('Bro Notice Index')
    shortname = l_('notices')

    def generate(self, docnames=None):
        content = {}

        if 'notices' not in self.domain.env.domaindata['bro']:
            return content, False

        for n in self.domain.env.domaindata['bro']['notices']:
            modname = n[0].split("::")[0]
            entries = content.setdefault(modname, [])
            entries.append([n[0], 0, n[1], n[2], '', '', ''])

        content = sorted(content.items())

        return content, False

class BroDomain(Domain):
    """Bro domain."""
    name = 'bro'
    label = 'Bro'

    object_types = {
        'type':             ObjType(l_('type'),             'type'),
        'namespace':        ObjType(l_('namespace'),        'namespace'),
        'id':               ObjType(l_('id'),               'id'),
        'keyword':          ObjType(l_('keyword'),          'keyword'),
        'enum':             ObjType(l_('enum'),             'enum'),
        'attr':             ObjType(l_('attr'),             'attr'),
    }

    directives = {
        'type':             BroGeneric,
        'namespace':        BroNamespace,
        'id':               BroIdentifier,
        'keyword':          BroKeyword,
        'enum':             BroEnum,
        'attr':             BroAttribute,
    }

    roles = {
        'type':             XRefRole(),
        'namespace':        XRefRole(),
        'id':               XRefRole(),
        'keyword':          XRefRole(),
        'enum':             XRefRole(),
        'attr':             XRefRole(),
        'see':              XRefRole(),
    }

    indices = [
        BroNotices,
    ]

    initial_data = {
        'objects': {},  # fullname -> docname, objtype
    }

    def clear_doc(self, docname):
        for (typ, name), doc in self.data['objects'].items():
            if doc == docname:
                del self.data['objects'][typ, name]

    def resolve_xref(self, env, fromdocname, builder, typ, target, node,
                     contnode):
        objects = self.data['objects']
        if typ == "see":
            if target not in self.data['idtypes']:
                self.env.warn(fromdocname,
                              'unknown target for ":bro:see:`%s`"' % (target))
                return []
            objtype = self.data['idtypes'][target]
            return make_refnode(builder, fromdocname,
                                        objects[objtype, target],
                                        objtype + '-' + target,
                                        contnode, target + ' ' + objtype)
        else:
            objtypes = self.objtypes_for_role(typ)
            for objtype in objtypes:
                if (objtype, target) in objects:
                    return make_refnode(builder, fromdocname,
                                        objects[objtype, target],
                                        objtype + '-' + target,
                                        contnode, target + ' ' + objtype)
                else:
                    self.env.warn(fromdocname,
                        'unknown target for ":bro:%s:`%s`"' % (typ, target))

    def get_objects(self):
        for (typ, name), docname in self.data['objects'].items():
            yield name, name, typ, docname, typ + '-' + name, 1
