'''
Extension for enhancing sphinx documentation generation for cython module
'''

import re
import types
import sys
from os.path import dirname, join
import sphinx
from sphinx.ext.autodoc import MethodDocumenter

class CythonMethodDocumenter(MethodDocumenter):
    # XXX i don't understand the impact of having a priority more than the
    # attribute or instance method but the things is, if it's a cython module,
    # the attribute will be prefer over method.
    priority = 12

def is_cython_extension(what, obj):
    # try to check if the first line of the doc is a signature
    doc = obj.__doc__
    if not doc:
        return False
    doc = doc.split('\n')
    if not len(doc):
        return False
    doc = doc[0]

    # test for cython cpdef
    if what in ('attribute', 'method') and hasattr(obj, '__objclass__'):
        if not re.match('^([a-zA-Z_][a-zA-Z0-9_]*)\.([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)', doc):
            return False
        return True
    # test for cython class
    if what == 'class' and hasattr(obj, '__pyx_vtable__'):
        if not re.match('^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)', doc):
            return False
        return True
    # test for python method in cython class
    if what in ('method', 'function') and obj.__class__ == types.BuiltinFunctionType:
        if not re.match('^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)', doc):
            return False
        return True

def callback_docstring(app, what, name, obj, options, lines):
    if what == 'module':
        # remove empty lines
        while len(lines):
            line = lines[0].strip()
            if not line.startswith('.. _') and line != '':
                break
            lines.pop(0)

        # if we still have lines, remove the title
        if len(lines):
            lines.pop(0)

        # if the title is followed by a separator, remove it.
        if len(lines) and lines[0].startswith('=='):
            lines.pop(0)

    elif is_cython_extension(what, obj) and lines:
        if what == 'class':
            lines.pop(0)
        line = lines.pop(0)

        # trick to realign the first line to the second one.
        # FIXME: fail if we finishing with::
        line_with_text = [x for x in lines if len(x.strip())]
        if len(line_with_text) and line is not None and len(lines):
            l = len(line_with_text[0]) - len(line_with_text[0].lstrip())
        else:
            l = 0
        lines.insert(0, ' ' * l + line)

        # calculate the minimum space available
        min_space = 999
        for line in lines:
            if not line.strip():
                continue
            min_space = min(min_space, len(line) - len(line.lstrip()))

        # remove that kind of space now.
        if min_space > 0:
            spaces = ' ' * min_space
            for idx, line in enumerate(lines):
                if not line.strip():
                    continue
                if not line.startswith(spaces):
                    continue
                lines[idx] = line[min_space:]

def callback_signature(app, what, name, obj, options, signature,
                       return_annotation):
    # remove the first 'self' argument, because python autodoc don't
    # add it for python method class. So do the same for cython class.
    if is_cython_extension(what, obj):
        try:
            doc = obj.__doc__.split('\n').pop(0)
            doc = '(%s' % doc.split('(')[1]
            doc = doc.replace('(self, ', '(')
            doc = doc.replace('(self)', '( )')
            return (doc, None)
        except AttributeError:
            pass
        except IndexError:
            pass

def setup(app):
    import kivy
    sys.path += [join(dirname(kivy.__file__), 'extras')]
    from highlight import KivyLexer

    if sphinx.version_info[0] >= 3:
        app.add_lexer('kv', KivyLexer)
    else:
        app.add_lexer('kv', KivyLexer())
    app.add_autodocumenter(CythonMethodDocumenter)
    app.connect('autodoc-process-docstring', callback_docstring)
    app.connect('autodoc-process-signature', callback_signature)
