import sys, re

macros = dict()
typedefs = dict()
functions = dict()
typedefs = dict()
deprecated = dict()
docstrings = dict()

class DocString:

    def __init__(self, string):
        self.docstring = string

    def to_rst(self):
        s = self.docstring

        # reference macros
        for m in macros:
            s = s.replace(m, ":c:macro:`{}`".format(m))

        # reference functions
        for f in functions:
            # exactly match function name (no substrings in other names)
            s = re.sub(r'{}((?![a-z0-9_]))'.format(f),
                       r':c:func:`{}`\1'.format(f), s)
            # all functions with _noref will not be referenced
            s = s.replace("{}_noref".format(f), f)

        return s

    def __str__(self):
        return self.docstring

    def __repr__(self):
        return self.__str__()

class Macro:

    def __init__(self, definition):
        l = definition.split()
        self.name = l[1].strip()
        assert("BOOLECTOR_" in self.name)

    def to_rst(self):
        return ".. c:macro:: {}".format(self.name)

class TypeDef:

    def __init__(self, definition):
        l = definition.replace(';', '').split()
        assert(len(l) == 4)
        self.name = l[-1]

    def to_rst(self):
        return ".. c:type:: {}".format(self.name)


class FunDef:

    def __init__(self, signature):
        p_name = re.compile(r'boolector_([a-z0-9_]+)') 
        m = p_name.search(signature)
        assert(m is not None)
        assert(m.group(0))
        self.name = m.group(0)
        self.signature = signature

        p_params = re.compile(r'\((.*)\)')
        m = p_params.search(signature)
        params = m.group(1).split(',')

        self.params = dict()

        # extract param with type
        if len(params) > 0 and params[0] != "void":
            for param in params:
                p = param.split()
                name = p[-1]
                type = " ".join(p[:-1])
                assert(name not in self.params)
                self.params[name] = type 

    def to_rst(self):
        return ".. c:function:: {}".format(self.signature.replace(';', ''))

    def __str__(self):
        return self.signature

    def __repr__(self):
        return self.__str__()

if __name__ == "__main__":

    if len(sys.argv) != 2:
        raise Exception("Invalid number of arguments")

    with open(sys.argv[1], "r") as file:
        fun_open = False
        doc_open = False
        fun = []
        doc = []
        linenum = 0
        for line in file:
            linenum += 1

            assert(not fun_open or not doc_open)
            assert(not doc_open or not fun_open)

            if line.strip().startswith("/*!"):  # start doc string
                if line.strip() != "/*!":
                    raise Exception(
                        "Invalid start of docstring at line {}. Docstring "\
                        "must start with '/*!' only".format(linenum))
                doc = []
                doc_open = True
            elif doc_open and line.strip().startswith("*/"):  # end doc string
                if line.strip() != "*/":
                    raise Exception(
                        "Invalid end of docstring at line {}. Docstring "\
                        "must end with '*/' only".format(linenum))
                doc_open = False
            elif doc_open: # contents doc string
                doc.append(line)
            elif not doc_open and "boolector_" in line:  # open function
                fun = []
                fun_open = True

            # collect function signature
            if fun_open:
                fun.append(line.strip())

            if fun_open and ");" in line:  # end function
                fun_open = False
                fd = FunDef(" ".join(fun))
                if len(doc) == 0:
                    raise Exception(
                        "No docstring found for function '{}'".format(fd.name))
                ds = DocString("".join(doc))
                if ".. deprecated::" in ds.docstring:
                    deprecated[fd.name] = fd
                else:
                    functions[fd.name] = fd
                docstrings[fd.name] = ds
                fun = []
                doc = []
            elif line.startswith("#define") and "BOOLECTOR_" in line and \
                 len(line.split()) > 2:
                m = Macro(line)
                ds = DocString("".join(doc))
                macros[m.name] = m 
                docstrings[m.name] = ds
                doc = []
            elif "typedef" in line and ("BoolectorNode" in line or \
                                        "BoolectorSort" in line or \
                                        "Btor " in line):
                td = TypeDef(line)
                ds = DocString("".join(doc))
                typedefs[td.name] = td
                docstrings[td.name] = ds
                doc = []

    with open('cboolector_index.rst', 'w') as file:
        file.write("C Interface\n")
        file.write("===========\n\n")

        file.write(".. _macros:\n\n")
        file.write("Macros\n")
        file.write("^^^^^^\n\n")
        for m in sorted(macros.keys()):
            file.write("{}\n\n".format(macros[m].to_rst()))
            for line in docstrings[m].to_rst().split('\n'):
                file.write("{}\n".format(line))
            file.write("\n")

        file.write("Typedefs\n")
        file.write("^^^^^^^^\n\n")
        for td in sorted(typedefs.keys()):
            file.write("{}\n\n".format(typedefs[td].to_rst()))
            for line in docstrings[td].to_rst().split('\n'):
                file.write("{}\n".format(line))
            file.write("\n")

        file.write("Functions\n")
        file.write("^^^^^^^^^\n\n")
        for f in sorted(functions.keys()):
            file.write("{}\n\n".format(functions[f].to_rst()))
            for line in docstrings[f].to_rst().split('\n'):
                file.write("{}\n".format(line))
            file.write("\n")

        file.write("Deprecated\n")
        file.write("^^^^^^^^^^\n\n")
        for f in sorted(deprecated.keys()):
            file.write("{}\n\n".format(deprecated[f].to_rst()))
            for line in docstrings[f].to_rst().split('\n'):
                file.write("{}\n".format(line))
            file.write("\n")
    print("generated cboolector_index.rst")

    sys.exit(0)
