import os
import sys
import traceback
import uuid
from collections import defaultdict

from conans.client.output import ScopedOutput
from conans.client.tools.files import chdir
from conans.errors import ConanException, NotFoundException
from conans.util.files import save

attribute_checker_hook = """
def pre_export(output, conanfile, conanfile_path, reference, **kwargs):
    # Check basic meta-data
    for field in ["url", "license", "description"]:
        field_value = getattr(conanfile, field, None)
        if not field_value:
            output.warn("Conanfile doesn't have '%s'. It is recommended to add it as attribute"
                        % field)
"""

valid_hook_methods = ["pre_export", "post_export",
                      "pre_source", "post_source",
                      "pre_build", "post_build",
                      "pre_package", "post_package",
                      "pre_upload", "post_upload",
                      "pre_upload_recipe", "post_upload_recipe",
                      "pre_upload_package", "post_upload_package",
                      "pre_download", "post_download",
                      "pre_download_recipe", "post_download_recipe",
                      "pre_download_package", "post_download_package"]


class HookManager(object):

    def __init__(self, hooks_folder, hook_names, output):
        self._hooks_folder = hooks_folder
        self._hook_names = hook_names
        self.hooks = defaultdict(list)
        self.output = output

    def create_default_hooks(self):
        attribute_checker_path = os.path.join(self._hooks_folder, "attribute_checker.py")
        save(attribute_checker_path, attribute_checker_hook)

    def execute(self, method_name, **kwargs):
        if not os.path.exists(os.path.join(self._hooks_folder, "attribute_checker.py")):
            self.create_default_hooks()
        if not self.hooks:
            self.load_hooks()

        assert method_name in valid_hook_methods
        for name, method in self.hooks[method_name]:
            try:
                output = ScopedOutput("[HOOK - %s] %s()" % (name, method_name), self.output)
                method(output, **kwargs)
            except Exception as e:
                raise ConanException("[HOOK - %s] %s(): %s\n%s" % (name, method_name, str(e),
                                                                   traceback.format_exc()))

    def load_hooks(self):
        for name in self._hook_names:
            self.load_hook(name)

    def load_hook(self, hook_name):
        filename = "%s.py" % hook_name
        hook_path = os.path.join(self._hooks_folder, filename)
        try:
            hook = HookManager._load_module_from_file(hook_path)
            for method in valid_hook_methods:
                hook_method = getattr(hook, method, None)
                if hook_method:
                    self.hooks[method].append((hook_name, hook_method))
        except NotFoundException:
            self.output.warn("Hook '%s' not found in %s folder. Please remove hook "
                             "from conan.conf or include it inside the hooks folder."
                             % (filename, self._hooks_folder))
        except Exception as e:
            raise ConanException("Error loading hook '%s': %s" % (hook_path, str(e)))

    @staticmethod
    def _load_module_from_file(hook_path):
        """ From a given path, obtain the in memory python import module
        """
        if not os.path.exists(hook_path):
            raise NotFoundException
        filename = os.path.splitext(os.path.basename(hook_path))[0]
        current_dir = os.path.dirname(hook_path)

        try:
            sys.path.append(current_dir)
            old_modules = list(sys.modules.keys())
            with chdir(current_dir):
                sys.dont_write_bytecode = True
                loaded = __import__(filename)
            # Put all imported files under a new package name
            module_id = uuid.uuid1()
            added_modules = set(sys.modules).difference(old_modules)
            for added in added_modules:
                module = sys.modules[added]
                if module:
                    try:
                        folder = os.path.dirname(module.__file__)
                    except AttributeError:  # some module doesn't have __file__
                        pass
                    else:
                        if folder.startswith(current_dir):
                            module = sys.modules.pop(added)
                            sys.modules["%s.%s" % (module_id, added)] = module
        except Exception:
            trace = traceback.format_exc().split('\n')
            raise ConanException("Unable to load Hook in %s\n%s" % (hook_path,
                                                                      '\n'.join(trace[3:])))
        finally:
            sys.dont_write_bytecode = False
            sys.path.pop()

        return loaded

@@ -2516,28 +2516,75 @@
 e):%0A        
-file
+if not hook_name.endswith(%22.py%22):%0A            hook_
 name = %22%25s.p
@@ -2610,32 +2610,49 @@
     hook_path = 
+os.path.normpath(
 os.path.join(sel
@@ -2668,25 +2668,27 @@
 folder, 
-file
+hook_
 name)
+)
 %0A       
@@ -3083,16 +3083,32 @@
 ve hook 
+from conan.conf 
 %22%0A      
@@ -3131,32 +3131,16 @@
        %22
-from conan.conf 
 or inclu
@@ -3170,17 +3170,35 @@
 folder.%22
-%0A
+ %25 (hook_name,%0A    
         
@@ -3222,20 +3222,47 @@
     
-%25 (filename,
+                                       
  sel
@@ -4905,18 +4905,16 @@
         
-  
 '%5Cn'.joi
@@ -5016,17 +5016,16 @@
 h.pop()%0A
-%0A
         

Allow folder syntax in conf for hooks