"""
F90 makefile dependency generator

inspired by fmkmf, a perl script to generate Makefile
available at https://www.geos.ed.ac.uk/homes/hcp/fmkmf
"""
import re
import os
import os.path
import io

use_mod_re = re.compile(r"^[ \t]*use[ \t]+(\w+)",re.IGNORECASE)
use_inc_re = re.compile(r"^[\#]?[ \t]*include[ \t]+['\"]([\.\-\w]+)",re.IGNORECASE)
def_mod_re = re.compile(r"^[ \t]*module[ \t]+(\w+)", re.IGNORECASE)
def_prog_re = re.compile(r"^[ \t]*program[ \t]+(\w+)", re.IGNORECASE)


# Map source files to module used and included files
sourcefiles_use_and_inc = dict()
# Map modules to the source file in which they are defined
module_sourcefile = dict()
# Map programs to the source file in which they are defined
program_sourcefile = dict()
# Map source files to their dependencies
sourcefiles_dependencies = dict()

def process_fsource(sourcepath):
    """Process a fortran 90 source file

    1 - Create a list of modules used by this source file
    2 - For each module, look for the source file where it is defined
    3 - Create a list of file dependency for this source file
    4 - Recursively process each module source file

    Parameters
    ----------
    sourcepath : string
        Path to the source file to process
    searchpath : string
        Path to the directories where source files are located

    """
    if (sourcepath not in sourcefiles_use_and_inc):
        # Create empty sets for used modules and included files
        sourcefiles_use_and_inc[sourcepath] = {"use":set(), "include":set(), "defines":set()}

        # Parse the sourcefile
        with io.open(sourcepath, 'r', encoding='utf8') as f:
            for line in f:
                # is there a module used?
                m = use_mod_re.match(line)
                if m:
                    mod_name = m.group(1)
                    print("{:s} uses module {:s}".format(sourcepath, mod_name))
                    sourcefiles_use_and_inc[sourcepath]["use"].add(mod_name)

                # is there an included file?
                m = use_inc_re.match(line)
                if m:
                    inc_name = m.group(1)
                    print("{:s} include file {:s}".format(sourcepath, inc_name))
                    src_dir = os.path.dirname(sourcepath)
                    inc_path = os.path.join(src_dir,inc_name)
                    sourcefiles_use_and_inc[sourcepath]["include"].add(inc_path)

                # is there a module defined ?
                m = def_mod_re.match(line)
                if m:
                    mod_name = m.group(1)
                    print("{:s} defines module {:s}".format(sourcepath, mod_name))
                    sourcefiles_use_and_inc[sourcepath]["defines"].add(mod_name)
                    module_sourcefile[mod_name] = sourcepath

                # is there a program defined ?
                m = def_prog_re.match(line)
                if m:
                    prog_name = m.group(1)
                    print("{:s} defines program {:s}".format(sourcepath, prog_name))
                    program_sourcefile[prog_name] = sourcepath


def build_dependencies(srcpath):
    """Build map of source files to their dependencies"""

    print("Build dependencies for {:s}".format(srcpath))
    base, ext = os.path.splitext(srcpath)
    if ext == ".pf":
        srcobj = base + "_pf.o"
    else:
        srcobj = base + ".o"
    sourcefiles_dependencies[srcobj] = set()
    for module in sourcefiles_use_and_inc[srcpath]["use"]:
        print("{:s} uses {:s}".format(srcpath, module))
        try :
            modfile = module_sourcefile[module]
        except KeyError:
            print("Module {:s} was not found in the source directory".format(module))
            modfile = None

        if modfile:
            base, ext = os.path.splitext(modfile)
            modobj = base + ".o"
            sourcefiles_dependencies[srcobj].add(modobj)

    for include in sourcefiles_use_and_inc[srcpath]["include"]:
        print("{:s} includes {:s}".format(srcpath, include))
        sourcefiles_dependencies[srcobj].add(include)
        print(sourcefiles_dependencies[srcobj])

        # src_obj depends on the module used by the included files !
        if include in sourcefiles_use_and_inc:
            for module in sourcefiles_use_and_inc[include]["use"]:
                print("{:s} uses {:s}".format(srcpath, module))
                try :
                    modfile = module_sourcefile[module]
                except KeyError:
                    print("Module {:s} was not found in the source directory".format(module))
                    modfile = None

                if modfile:
                    base, ext = os.path.splitext(modfile)
                    modobj = base + ".o"
                    sourcefiles_dependencies[srcobj].add(modobj)

def build_program_dependencies(src_list):
    """Build dependencies for a program

    prog_name: string
        Name of the main program

    src_dir: string
        Path to the source directory (subdirectories will be visited)

    src_ext : list of string
        list of extension to select source files
    """
    # Process each source file in the source directories
    for srcpath in src_list:
        process_fsource(srcpath)

    for srcpath in src_list:
        build_dependencies(prog_file)


def write_makefile_dependencies(depfile):

    with open(depfile,'w') as f:
        for srcobj, dependencies in sorted(sourcefiles_dependencies.items()):
            inc_list = []
            for dep in sorted(dependencies):
                base, ext = os.path.splitext(dep)
                print("{:s}={:s}+{:s}".format(dep,base,ext))
                if ext == ".o":
                    f.write("$(build_dir)/{:s}: $(build_dir)/{:s}\n".format(srcobj, dep))
                if ext == ".inc":
                    inc_list.append(dep)
            print("{:s} {}".format(srcobj, inc_list))
            for inc in inc_list:
                f.write("$(build_dir)/{:s}: {:s}\n".format(srcobj, inc))
            if dependencies:
                f.write("\n")

if __name__ == "__main__":
    import sys

    ext_list = [".f90", ".F90", ".inc", ".pf"]
    src_list = sys.argv[1:]
    obj_list = []
    for src_path in src_list:
        base, ext = os.path.splitext(src_path)
        if ext != ".inc":
            obj_list.append(src_path)

    print(src_list)
    for src_path in src_list:
        process_fsource(src_path)
    for src_path in obj_list:
        build_dependencies(src_path)

    write_makefile_dependencies("dependencies.mk")
