# -*- coding: utf-8 -*-
"""Script to extract documentation from docstrings in *.h files in the DOLFIN
source tree."""

# Copyright (C) 2010 Anders Logg
#
# This file is part of DOLFIN.
#
# DOLFIN is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DOLFIN is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DOLFIN. If not, see <http://www.gnu.org/licenses/>.
#
# Modified by Kristian B. Oelgaard, 2011.
# Modified by Marie E. Rognes, 2011.
# Modified by Anders E. Johansen, 2011.
#
# First added:  2010-08-26
# Last changed: 2011-07-10

import os, io

def extract_documentation(dolfin_dir, header, module):
    "Extract documentation for given header in given module"

    # print "Extracting documentation for %s..." % header

    # List of classes with documentation
    classnames = []
    documentation = []

    # Class name and parent class name
    classname = None
    parents = None

    # Comment and signature
    comment = None
    signature = None

    # Indentation of signatures
    indent = 0

    # Iterate over each line
    f = io.open(os.path.join(dolfin_dir, "dolfin", module, header), encoding="utf-8")
    for line in f:

        # Check for comment
        if "///" in line:

            # We may have either "///" and "/// "
            if "/// " in line:
                c = line.split("/// ")[1].rstrip()
            else:
                c = line.split("///")[1].rstrip()

            # Found start of new comment
            if comment is None:
                comment = c

            # Continuing comment on next line
            else:
                comment += "\n" + c

        # Check for class
        # If anything goes wrong, remove the last '<' after template.
        elif " class " in line and not ";" in line and not "//" in line and not "template<" in line:

            # Get class name and parent
            classname = line.split(" class ")[1].split(":")[0].strip()
            if "public" in line:
                # Strip of each parent of additional commas, blanks
                # and newlines
                parents = [p.strip(", \n") for p in line.split("public")[1:]]

                # Remove virtual modifier
                parents = [p.replace("virtual ", "") for p in parents]

            # Store documentation
            # TODO: KBO: we don't check if a given classname is in the dolfin
            # namepace.
            classnames.append(classname)
            documentation.append((classname, parents, comment, []))

            classname = None
            parents = None
            comment = None

        # If we encounter a '//' commented line we reset comment and proceed
        # This means that '///' and '//' should not be mixed when documenting
        # functions.
        elif line.lstrip()[0:2] == "//" and not line.lstrip()[0:3] == "///":
            comment = None
            continue

        # Check for function signature
        elif comment is not None:
            s = line.strip()

            # Found start of new signature
            if signature is None:
                signature = s
                #indent = (len(s.split("(")[0]) + 1)*" "

            # Continuing signature on next line
            else:
                #signature += "\n" + indent + s
                signature += " " + s

            # Signature ends when we find ";" or "{"
            if ";" in s or "{" in s:

                # Strip out last part
                signature = signature.split(";")[0]
                signature = signature.split("{")[0]
                signature = signature.strip()

                # Remove stuff Sphinx can't handle
                signature = signature.replace("virtual ", "")
                signature = signature.replace("inline ", "")

                # Remove ": stuff" for constructors
                new_s = []
                for l in signature.split("::"):
                    if not ":" in l:
                        new_s.append(l)
                    else:
                        new_s.append(l.split(":")[0])
                        break
                signature = "::".join(new_s).strip()

                # Remove template stuff (not handled correctly by Sphinx)
                # e.g., 'template <typename Kernel> CGAL::Bbox_3  bbox()' in mesh/Point.h
                if "template" in signature:
                    signature = ">".join(signature.split(">")[1:]).lstrip()

                # Only handle functions or enums, i.e. signatures that contain
                # '(' (or ')'). This is to avoid picking up data members.
                # NOTE, KBO: Should we also skip private functions?
                if not "(" in signature and not "enum" in signature:
                    # Reset comment and signature
                    comment = None
                    signature = None
                    continue

                # Skip destructors (not handled by Sphinx)
                destructor = "~" in signature

                # Get function name
                #function = signature.split("(")[0].split(" ")[-1]

                # Store documentation
                if len(documentation) > 0 and not destructor:
                    documentation[-1][-1].append((signature, comment))
                elif not destructor:
                    documentation = [(None, None, None, [(signature, comment)])]

                # Reset comment and signature
                comment = None
                signature = None

    # Close file
    f.close()

    # Sort documentation alphabetically within each class
#    for (classname, parent, comment, function_documentation) in documentation:
#        function_documentation.sort()

    return documentation, classnames

def extract_doc_representation(dolfin_dir):
    # Extract modules from dolfin.h
    modules = []
    f = open(os.path.join(dolfin_dir, "dolfin", "dolfin.h"))
    for line in f:
        if line.startswith("#include <dolfin/"):
            module = line.split("/")[1]
            modules += [module]
    f.close()

    # Iterate over modules
    documentation = {}
    classnames = []
    for module in modules:
#        if not module == "la":
#            continue
        # Extract header files from dolfin_foo.h
        f = open(os.path.join(dolfin_dir, "dolfin", module, "dolfin_%s.h" % module))
        documentation[module] = []
        for line in f:

            # Generate documentation for header file
            if line.startswith("#include <dolfin/"):
                header = line.split("/")[2].split(">")[0]
                # Skip version.h (generated from version.h.in via CMake)
                if header == "version.h":
                    continue
#                if not header == "GenericTensor.h":
#                    continue
                doc, cls = extract_documentation(dolfin_dir, header, module)
                documentation[module].append((header, doc))
                classnames += cls

    return documentation, classnames

if __name__ == "__main__":
    docs, classes = extract_doc_representation()
#    for c in classes:
#        print c
#    for key, doc in docs.items():
#        for header, cont in doc:
#            print cont



