# -*- coding: utf-8 -*-
# Utilities for parsing DOLFIN files and generaton of SWIG files
#
# Copyright (C) 2012 Johan Hake
#
# 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/>.
#
# First added:  2012-07-01
# Last changed: 2012-07-03

# System imports
import os
import re

try:
    from collections import OrderedDict
except ImportError:
    from dolfin_utils.ordereddict import OrderedDict

# Local imports
from .CppHeaderParser import CppHeader

# reg exp pattern
_template_pattern = re.compile("\w+<([\w ]+)>")

def strip_templates(types):
    """
    Strip template types
    """
    ret_types = set()
    temp_types = re.findall(_template_pattern, types)
    while temp_types:
        ret_types.update(temp_types)
        for temp_type in temp_types:
            types = types.replace("<%s>" % temp_type, "")
        temp_types = re.findall(_template_pattern, types)

    if types:
        ret_types.add(types)

    return ret_types

def build_swig_import_info(dependencies, submodule_info, module_prepend="",
                           package_name="", parsed_modules=None):
    """
    Build import and include info from a list of submodules and headers
    """
    import_lines = []
    headers_includes = ["", "// Include types from dependent modules"]
    file_dependencies = []

    # Iterate over the file dependencies and generate import statements
    previous_submodule = ""
    for submodule, headers in list(dependencies.items()):
        module = submodule_info[submodule]["module"]
        headers_includes.append("")
        headers_includes.append("// #include types from %s submodule of "\
                                "module %s" % (submodule, module))
        import_lines.append("")
        import_lines.append("// %%import types from submodule"\
                            " %s of SWIG module %s" % \
                            (submodule, module))
        
        # Check for pre includes
        if submodule_info[submodule]["has_pre"]:
            file_dependencies.append("dolfin/swig/%s/pre.i" % submodule)
            import_lines.append(\
                "%%include \"dolfin/swig/%s/pre.i\"" % submodule)

        # Add headers
        file_dependencies.extend(headers)
        for header in headers:
            # Add file dependency
            if parsed_modules is None or module in parsed_modules:
                
                # If module is not parsed yet we introduce circular dependencies
                # in import statments which we want to avoid
                import_lines.append("%%import(package=\"%s\", module=\"%s\") \"%s\"" % \
                                    (package_name, module_prepend+module, header))
            headers_includes.append("#include \"%s\"" % header)

    return import_lines, headers_includes, file_dependencies

def parse_and_extract_type_info(code):
    """
    Parse header code and return declared types, used types, and any bases
    """
    
    used_types=set()
    declared_types=OrderedDict()

    # Use CppHeader to extract
    cppheader = CppHeader(code, argType="string")

    # Collect used types from the code
    used_types = set()

    # Iterate over typedefs and collect types
    for typedef in sorted(cppheader.typedefs):
        if "dolfin::" in typedef:
            typedef = typedef.replace("dolfin::", "")

        # Store type information
        declared_types[typedef] = []

    # Iterate over free functions and collect dependant types
    for function in cppheader.functions:
    
        # Check return type
        if function["rtnType"] != "void":
            used_types.update(strip_templates(function["rtnType"]))
    
        # Check argument types
        for argument in function["parameters"]:
            used_types.update(strip_templates(argument["type"]))

    # Iterate over class and collect info
    for class_name, class_repr in sorted(cppheader.classes.items()):

        # Check if class is private
        if class_repr["parent"] is not None:
            continue
    
        # Get bases
        bases = set()
        for base in class_repr["inherits"]:
            bases.update(strip_templates(base["class"]))

        # Remove itself from any bases
        bases.difference_update([class_name])

        # Register the class
        declared_types[class_name] = sorted(bases)
        
        # Iterate over public properties
        for prop in class_repr["properties"]["public"]:
            used_types.update(strip_templates(prop["type"]))

        # Iterate over methods and collect dependant types
        for method in class_repr["methods"]["public"]+\
                class_repr["methods"]["protected"]:
    
            # Check return type
            if method["rtnType"] != "void":
                used_types.update(strip_templates(method["rtnType"]))
    
            # Check argument types
            for argument in method["parameters"]:
                used_types.update(strip_templates(argument["type"]))
    
        # Remove any self dependencies
        used_types = set(used_type for used_type in
                         used_types if class_name not in used_type)

    return used_types, declared_types
    
def sort_submodule_dependencies(dependencies, submodule_info):
    """
    Given a dict of submodules and headers, we use original
    submodule info to sort the content
    """
    submodule_dependences = OrderedDict()
    for submodule in submodule_info:
        if submodule in dependencies:
            
            # Register sorted dependency
            submodule_dependences[submodule] = [header for header in \
                submodule_info[submodule]["headers"] \
                if header in dependencies[submodule]]
    
    return submodule_dependences

