# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import ast
import _ast
import unittest
import os
import sys

import oscrypto as module


# This handles situations where an import is importing a function from a
# dotted path, e.g. "from . import ident", and ident is a function, not a
# submodule
MOD_MAP = {
    'oscrypto._backend_config': 'oscrypto',
    'oscrypto.backend': 'oscrypto',
    'oscrypto.ffi': 'oscrypto'
}


def add_mod(mod_name, imports):
    """
    Maps pre-defined module.function to module import names

    :param mod_name:
        A unicode string of a fully-qualified module name being imported

    :param imports:
        A set of unicode strings of the modules that are being imported
    """

    imports.add(MOD_MAP.get(mod_name, mod_name))


def walk_ast(parent_node, modname, imports):
    """
    Walks the AST for a module finding any imports and recording them

    :param parent_node:
        A node from the _ast module

    :param modname:
        A unicode string of the module we are walking the AST of

    :param imports:
        A set of unicode strings of the imports that have been found so far
    """

    for node in ast.iter_child_nodes(parent_node):
        if isinstance(node, _ast.Import):
            if node.names[0].name.startswith(module.__name__):
                add_mod(node.names[0].name, imports)

        elif isinstance(node, _ast.ImportFrom):
            if node.level > 0:
                if modname == module.__name__:
                    base_mod = module.__name__
                else:
                    base_mod = '.'.join(modname.split('.')[:-node.level])
                if node.module:
                    base_mod += '.' + node.module
            else:
                base_mod = node.module

            if not base_mod.startswith(module.__name__):
                continue

            if node.level > 0 and not node.module:
                for n in node.names:
                    add_mod(base_mod + '.' + n.name, imports)
            else:
                add_mod(base_mod, imports)

        elif isinstance(node, _ast.If):
            for subast in node.body:
                walk_ast(subast, modname, imports)
            for subast in node.orelse:
                walk_ast(subast, modname, imports)

        elif sys.version_info >= (3, 3) and isinstance(node, _ast.Try):
            for subast in node.body:
                walk_ast(subast, modname, imports)
            for subast in node.orelse:
                walk_ast(subast, modname, imports)
            for subast in node.finalbody:
                walk_ast(subast, modname, imports)

        elif sys.version_info < (3, 3) and isinstance(node, _ast.TryFinally):
            for subast in node.body:
                walk_ast(subast, modname, imports)
            for subast in node.finalbody:
                walk_ast(subast, modname, imports)

        elif sys.version_info < (3, 3) and isinstance(node, _ast.TryExcept):
            for subast in node.body:
                walk_ast(subast, modname, imports)
            for subast in node.orelse:
                walk_ast(subast, modname, imports)


class InitTests(unittest.TestCase):

    def test_load_order(self):
        deps = {}

        mod_root = os.path.abspath(os.path.dirname(module.__file__))
        files = []
        for root, dnames, fnames in os.walk(mod_root):
            for f in fnames:
                if f.endswith('.py'):
                    full_path = os.path.join(root, f)
                    rel_path = full_path.replace(mod_root + os.sep, '')
                    files.append((full_path, rel_path))

        for full_path, rel_path in sorted(files):
            with open(full_path, 'rb') as f:
                full_code = f.read()
                if sys.version_info >= (3,):
                    full_code = full_code.decode('utf-8')

            modname = rel_path.replace('.py', '').replace(os.sep, '.')
            if modname == '__init__':
                modname = module.__name__
            else:
                modname = '%s.%s' % (module.__name__, modname)

            if sys.version_info < (3,) and sys.platform == 'win32' and b'\r\n' in full_code:
                full_code = full_code.replace(b'\r\n', b'\n')

            imports = set([])
            module_node = ast.parse(full_code, filename=full_path)
            walk_ast(module_node, modname, imports)

            deps[modname] = imports

        load_order = module.load_order()
        prev = set([])
        for mod in load_order:
            self.assertEqual(True, mod in deps)
            self.assertEqual((mod, set([])), (mod, deps[mod] - prev))
            prev.add(mod)
