class Registry:

    _doc = {}
    log = None
    config = None

    def __init__(self):
        self.config = SingleConfig()
        self.log = SingleLog()
        self._scan_for_yamls_in_project()

    def get_files(self):
        """
        :return objcect structured as:
            {
                "role_name":["/abs_path/to_file","/abs_path/to_file2"],
                "role2_name:["abs/path/2"]
            }
        :param
        :return:
        """
        return self._doc

    def _scan_for_yamls_in_project(self):
        """
        Search for Yaml files depending if we are scanning a playbook or a role
        :return:
        """
        base_dir = self.config.get_base_dir()
        base_dir_roles = base_dir + '/roles'

        if not self.config.is_role:

            self.log.debug('Scan for playbook files: ' + base_dir)
            self._scan_for_yamls(base_dir, is_role=False)

            self.log.debug('Scan for roles in the project: ' + base_dir_roles)
            for entry in os.scandir(base_dir_roles):
                try:
                    is_dir = entry.is_dir(follow_symlinks=False)
                except OSError as error:
                    print('Error calling is_dir():', error, file=sys.stderr)
                    continue
                if is_dir:
                    self._scan_for_yamls(entry.path)
        else:  # it is a role
            self.log.debug('Scan for files in a role: ' + base_dir)
            self._scan_for_yamls(base_dir)

    def _scan_for_yamls(self, base, is_role=True):
        """
        Search for the yaml files in each project/role root and append to the corresponding object
        :param base: directory in witch we are searching
        :param is_role: is this a role directory
        :return: None
        """
        extensions = YAML_EXTENSIONS
        base_dir = base

        for extension in extensions:
            for filename in glob.iglob(base_dir + '/**/*.' + extension,
                                       recursive=True):

                if self._is_excluded_yaml_file(filename,
                                               base_dir,
                                               is_role=is_role):
                    self.log.trace('Excluding: ' + filename)

                else:
                    if not is_role:
                        self.log.trace('Adding to playbook: ' + filename)
                        self.add_role_file(filename, PLAYBOOK_ROLE_NAME)
                    else:
                        role_dir = os.path.basename(base_dir)
                        self.log.trace('Adding to role:' + role_dir + ' => ' +
                                       filename)
                        self.add_role_file(filename, role_dir)

    def _is_excluded_yaml_file(self, file, role_base_dir=None, is_role=True):
        """
        sub method for handling file exclusions based on the full path starts with
        :param file:
        :param role_base_dir:
        :param is_role:
        :return:
        """
        if is_role:
            base_dir = role_base_dir
            excluded = self.config.excluded_roles_dirs.copy()
        else:
            base_dir = self.config.get_base_dir()
            excluded = self.config.excluded_playbook_dirs.copy()
            excluded.append('roles')

        is_filtered = False
        for excluded_dir in excluded:
            if file.startswith(base_dir + '/' + excluded_dir):
                return True
        return is_filtered

    def add_role_file(self, path, role_name):
        self.log.trace('add_role_file(' + path + ',' + role_name + ')')
        if role_name not in self._doc.keys():
            self._doc[role_name] = []

        self._doc[role_name].append(path)
class Generator:

    template_files = []
    extension = "j2"
    _parser = None

    def __init__(self, doc_parser):
        self.config = SingleConfig()
        self.log = SingleLog()
        self.log.info("Using template dir: " +
                      self.config.get_template_base_dir())
        self._parser = doc_parser
        self._scan_template()

    def _scan_template(self):
        """
        Search for Jinja2 (.j2) files to apply to the destination
        :return: None
        """

        base_dir = self.config.get_template_base_dir()

        for file in glob.iglob(base_dir + '/**/*.' + self.extension,
                               recursive=True):

            relative_file = file[len(base_dir) + 1:]
            if ntpath.basename(file)[:1] != "_":
                self.log.trace("[GENERATOR] found template file: " +
                               relative_file)
                self.template_files.append(relative_file)
            else:
                self.log.debug("[GENERATOR] ignoring template file: " +
                               relative_file)

    def _create_dir(self, dir):
        if not self.config.dry_run:
            os.makedirs(dir, exist_ok=True)
        else:
            self.log.info("[GENERATOR][DRY] Creating dir: " + dir)

    def _write_doc(self):
        files_to_overwite = []

        for file in self.template_files:
            doc_file = self.config.get_output_dir(
            ) + "/" + file[:-len(self.extension) - 1]
            if os.path.isfile(doc_file):
                files_to_overwite.append(doc_file)

        if len(files_to_overwite
               ) > 0 and self.config.template_overwrite is False:
            SingleLog.print("This files will be overwritten:",
                            files_to_overwite)
            if not self.config.dry_run:
                resulst = FileUtils.query_yes_no("do you want to continue?")
                if resulst != "yes":
                    sys.exit()

        for file in self.template_files:
            doc_file = self.config.get_output_dir(
            ) + "/" + file[:-len(self.extension) - 1]
            source_file = self.config.get_template_base_dir() + "/" + file

            self.log.trace("[GENERATOR] Writing doc output to: " + doc_file +
                           " from: " + source_file)

            # make sure the directory exists
            self._create_dir(os.path.dirname(os.path.realpath(doc_file)))

            if os.path.exists(source_file) and os.path.isfile(source_file):
                with open(source_file, 'r') as template:
                    data = template.read()
                    if data is not None:
                        try:
                            data = Environment(
                                loader=FileSystemLoader(
                                    self.config.get_template_base_dir()),
                                lstrip_blocks=True,
                                trim_blocks=True).from_string(data).render(
                                    self._parser.get_data(), r=self._parser)

                            if not self.config.dry_run:
                                with open(doc_file, 'w') as outfile:
                                    outfile.write(data)
                                    self.log.info("Writing to: " + doc_file)
                            else:
                                self.log.info("[GENERATOR][DRY] Writing to: " +
                                              doc_file)
                        except jinja2.exceptions.UndefinedError as e:
                            self.log.error(
                                "Jinja2 templating error: <" + str(e) +
                                "> when loading file: \"" + file +
                                "\", run in debug mode to see full except")
                            if self.log.log_level < 1:
                                raise
                        except UnicodeEncodeError as e:
                            self.log.error(
                                "At the moment I'm unable to print special chars: <"
                                + str(e) +
                                ">, run in debug mode to see full except")
                            if self.log.log_level < 1:
                                raise
                            sys.exit()

    def print_to_cli(self):

        for file in self.template_files:
            source_file = self.config.get_template_base_dir() + "/" + file
            with open(source_file, 'r') as template:
                data = template.read()

                if data is not None:
                    try:
                        data = Environment(
                            loader=FileSystemLoader(
                                self.config.get_template_base_dir()),
                            lstrip_blocks=True,
                            trim_blocks=True).from_string(data).render(
                                self._parser.get_data(), r=self._parser)
                        print(data)
                    except jinja2.exceptions.UndefinedError as e:
                        self.log.error(
                            "Jinja2 templating error: <" + str(e) +
                            "> when loading file: \"" + file +
                            "\", run in debug mode to see full except")
                        if self.log.log_level < 1:
                            raise
                    except UnicodeEncodeError as e:
                        self.log.error(
                            "At the moment I'm unable to print special chars: <"
                            + str(e) +
                            ">, run in debug mode to see full except")
                        if self.log.log_level < 1:
                            raise

                    except:
                        print("Unexpected error:", sys.exc_info()[0])
                        raise

    def render(self):
        if self.config.use_print_template:
            self.print_to_cli()
        else:
            self.log.info("Using output dir: " + self.config.get_output_dir())
            self._write_doc()