"""functions for external dependencies"""

import os
import filecmp

from collections import OrderedDict
from mod import log, util, registry, template
from mod.tools import git

#-------------------------------------------------------------------------------
def get_imports(fips_dir, proj_dir) :
    """get the imports from the fips.yml file in proj_dir

    :param proj_dir:    the project directory
    :returns:           dictionary object with imports (can be empty)
    """
    proj_name = util.get_project_name_from_dir(proj_dir)
    imports = {}
    if util.is_valid_project_dir(proj_dir) :
        dic = util.load_fips_yml(proj_dir)
        if 'imports' in dic :
            imports = dic['imports']

        # warn if this is an old-style list instead of new style dict
        if imports :
            if type(imports) is list :
                log.warn("imports in '{}/fips.yml' uses obsolete array format".format(proj_dir))
                
                # convert old style to new dict format
                # FIXME: should be removed after a while
                new_imports = {}
                for dep in imports :
                    dep_url = registry.get_url(fips_dir, dep)
                    if not util.is_git_url(dep_url) :
                        log.error("'{}' cannot be resolved into a git url (in project '{}')".format(dep_url, proj_name))
                    dep_proj_name = util.get_project_name_from_url(dep_url)
                    new_imports[dep_proj_name] = {}
                    new_imports[dep_proj_name]['git']    = util.get_giturl_from_url(dep_url)
                    new_imports[dep_proj_name]['branch'] = util.get_gitbranch_from_url(dep_url)
                imports = new_imports
            elif type(imports) is dict :
                for dep in imports :
                    if not 'branch' in imports[dep] :
                        imports[dep]['branch'] = 'master'
                    if not 'git' in imports[dep] :
                        log.error("no git URL in import '{}' in '{}/fips.yml'!\n".format(dep, proj_dir))
            else :
                log.error("imports in '{}/fips.yml' must be a dictionary!".format(proj_dir))
    return imports

#-------------------------------------------------------------------------------
def get_exports(proj_dir) :
    """get the exports from the fips.yml file in proj_dir

    :param proj_dir:    the project directory
    :returns:           dictionary object with exports (can be empty)
    """
    exports = {}
    if util.is_valid_project_dir(proj_dir) :
        dic = util.load_fips_yml(proj_dir)
        if 'exports' in dic :
            exports = dic['exports']
    if not 'header-dirs' in exports :
        exports['header-dirs'] = []
    if not 'lib-dirs' in exports :
        exports['lib-dirs'] = []
    if not 'defines' in exports :
        exports['defines'] = {}
    if not 'modules' in exports :
        exports['modules'] = {}
    return exports

#-------------------------------------------------------------------------------
def get_policy(proj_dir, policy) :
    """checks whether a policy is defined in the projects fips.yml
    and returns its bool value, or the default if not defined.

    :param proj_dir:    the project directory
    :param policy:      the policy string name
    :returns:           true/false
    """
    def_policies = {
        'no_auto_import': False
    }
    if util.is_valid_project_dir(proj_dir) :
        dic = util.load_fips_yml(proj_dir)
        if 'policies' in dic and type(dic['policies']) is dict:
            if policy in dic['policies'] :
                return dic['policies'][policy]
    # not found, return default
    if policy in def_policies :
        return def_policies[policy]
    else :
        # unknown policy, return None
        log.error("unknown policy name: '{}'".format(policy))
        return None

#-------------------------------------------------------------------------------
def _rec_get_all_imports_exports(fips_dir, proj_dir, result) :
    """recursively get all imported projects, their exported and
    imported modules in a dictionary object:
        
        project-1:
            url:    git-url (not valid for first, top-level project)
            exports:
                header-dirs: [ ]
                lib-dirs: [ ]
                defines: 
                    def-key: def-val
                    ...
                modules :
                    mod: dir
                    mod: dir
                ...
            imports:
                name:
                    git:    [git-url]
                    branch: [optional: branch or tag]
                name:
                    ...
                ...
        ...

    :param fips_dir:    absolute fips directory
    :param proj_dir:    absolute project directory
    :param result:      in/out current result
    :returns:           bool success, and modified result dictionary
    """
    success = True
    ws_dir = util.get_workspace_dir(fips_dir)
    proj_name = util.get_project_name_from_dir(proj_dir)
    if proj_name not in result :

        result[proj_name] = {}
        result[proj_name]['imports'] = get_imports(fips_dir, proj_dir)
        result[proj_name]['exports'] = get_exports(proj_dir)

        for dep_proj_name in result[proj_name]['imports'] :
            if dep_proj_name not in result :
                dep_proj_dir = util.get_project_dir(fips_dir, dep_proj_name)
                dep_url = result[proj_name]['imports'][dep_proj_name]['git']
                success, result = _rec_get_all_imports_exports(fips_dir, dep_proj_dir, result)

                # break recursion on error
                if not success :
                    return success, result

    # done
    return success, result

#-------------------------------------------------------------------------------
def get_all_imports_exports(fips_dir, proj_dir) :
    """recursively get all imports/exports of a project, fails if any
    dependencies haven't been fetched yet, see _rec_get_imports_exports()
    for result dictionary structure

    :param fips_dir:    absolute fips directory
    :param proj_dir:    absolute project directory
    :returns:           succes, and result dictionary object
    """
    result = OrderedDict()
    return _rec_get_all_imports_exports(fips_dir, proj_dir, result)

#-------------------------------------------------------------------------------
def _rec_fetch_imports(fips_dir, proj_dir, handled) :
    """internal recursive function to fetch project imports,
    keeps an array of already handled dirs to break cyclic dependencies

    :param proj_dir:    current project directory
    :param handled:     array of already handled dirs
    :returns:           updated array of handled dirs
    """
    ws_dir = util.get_workspace_dir(fips_dir)
    proj_name = util.get_project_name_from_dir(proj_dir)
    if proj_name not in handled :
        handled.append(proj_name)

        imports = get_imports(fips_dir, proj_dir)
        for dep in imports:
            dep_proj_name = dep
            if dep not in handled:
                dep_proj_dir = util.get_project_dir(fips_dir, dep_proj_name)
                log.colored(log.YELLOW, "=== dependency: '{}':".format(dep_proj_name))
                dep_ok = False
                if not os.path.isdir(dep_proj_dir) :
                    # directory did not exist, do a fresh git clone
                    dep = imports[dep_proj_name]
                    git_commit = None if 'rev' not in dep else dep['rev']
                    if git_commit :
                        if 'depth' in dep :
                            # when using rev, we may not want depth because the revision may not be reachable
                            log.colored(log.YELLOW, "=== 'depth' was ignored because parameter 'rev' is specified.")
                        dep['depth'] = None
                    git_depth = git.clone_depth if not git_commit and 'depth' not in dep else dep['depth']
                    git_url = dep['git']
                    git_branch = dep['branch']
                    if git.clone(git_url, git_branch, git_depth, dep_proj_name, ws_dir) :
                        if git_commit :
                            log.colored(log.YELLOW, "=== revision: '{}':".format(git_commit))
                            dep_ok = git.checkout(dep_proj_dir, git_commit)
                        else :
                            dep_ok = True
                    else :
                        log.error('failed to git clone {} into {}'.format(git_url, dep_proj_dir))
                else :
                    # directory already exists
                    log.info("dir '{}' exists".format(dep_proj_dir))
                    dep_ok = True

                # recuse
                if dep_ok :
                    handled = _rec_fetch_imports(fips_dir, dep_proj_dir, handled)

    # done, return the new handled array
    return handled

#-------------------------------------------------------------------------------
def fetch_imports(fips_dir, proj_dir) :
    """recursively git-clone the imports of a project, NOTE: existing
    repos will never be updated

    :param proj_dir:    existing project directory
    """
    _rec_fetch_imports(fips_dir, proj_dir, [])

#-------------------------------------------------------------------------------
def gather_imports(fips_dir, proj_dir) :
    """resolve imports of proj_dir and returns a big dictionary
    with all imported data, which can then be written with write_imports()

    :param fips_dir:    absolute fips directory
    :param proj_dir:    absolute project directory
    :param dry_run:     if true perform a dry run (used by 'fips diag')
    :returns:           a big dictionary which can be written with write_imports()
                        or None on error
    """
    imported = OrderedDict()
    ws_dir = util.get_workspace_dir(fips_dir)
    success, deps = get_all_imports_exports(fips_dir, proj_dir)

    unique_defines = {}
    unique_modules = {}
    
    if success :
        
        # for each project:
        for proj_name in deps :