#!/usr/bin/env python3

# pylint:disable=line-too-long
"""
The tool to check the availability or syntax of domains, IPv4 or URL.

::

    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝

This submodule will provide the configuration loading and construction logic.

Author:
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom

Special thanks:
    https://pyfunceble.readthedocs.io/en/master/special-thanks.html

Contributors:
    http://pyfunceble.readthedocs.io/en/master/special-thanks.html

Project link:
    https://github.com/funilrys/PyFunceble

Project documentation:
    https://pyfunceble.readthedocs.io/en/master/

Project homepage:
    https://funilrys.github.io/PyFunceble/

License:
::

    MIT License

    Copyright (c) 2017-2019 Nissar Chababy

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
"""
# pylint: enable=line-too-long
# pylint: disable=bad-continuation
import PyFunceble
from PyFunceble.helpers import Dict, Directory, Download, File
from PyFunceble.iana import IANA
from PyFunceble.publicsuffix import PublicSuffix

class Load:  # pylint: disable=too-few-public-methods
    """
    Help us load the configuration(s) file(s).

    :param path_to_config: The possible path to the configuration to load.
    :type path_to_config: str
    """

    def __init__(self, path_to_config):
        # We initiate 2 variables:
        #   * One with the path to the config file
        #   * The second one is the path to the default configuration file which is
        #   used only if the first one is not found.
        self.path_to_config, self.path_to_default_config = self._set_path_to_configs(
            path_to_config
        )

        try:
            # We try to load the configuration.
            self._load_config_file()
        except FileNotFoundError:
            # We got a FileNotFoundError

            if "PYFUNCEBLE_AUTO_CONFIGURATION" not in PyFunceble.environ:
                # `PYFUNCEBLE_AUTO_CONFIGURATION` is not into the environnements variables.

                while True:
                    # We infinitly loop until we get a reponse which is `y|Y` or `n|N`.

                    # We ask the user if we should install and load the default configuration.
                    response = input(
                        "%s was not found.\n\
Install and load the default configuration at the mentioned location? [y/n] "
                        % (
                            PyFunceble.Style.BRIGHT
                            + self.path_to_config
                            + PyFunceble.Style.RESET_ALL
                        )
                    )

                    if isinstance(response, str):
                        # The response is a string

                        if response.lower() == "y":
                            # The response is a `y` or `Y`.

                            # We install the production configuration.
                            self._install_production_config()

                            # We load the installed configuration.
                            self._load_config_file()

                            # And we break the loop as we got a satisfied response.
                            break

                        elif response.lower() == "n":
                            # The response is a `n` or `N`.

                            # We inform the user that something went wrong.
                            raise Exception("Unable to find the configuration file.")

            else:
                # `PYFUNCEBLE_AUTO_CONFIGURATION` is not into the environnements variables.

                # We install the production configuration.
                self._install_production_config()

                # We load the installed configuration.
                self._load_config_file()

        for main_key in ["domains", "hosts", "splited", "json"]:
            # We loop through the key which contain paths under the `outputs` index.

            # And we fix the path.
            # Which means: If they do not end with the directory separator, we append
            # it to the end.
            PyFunceble.CONFIGURATION["outputs"][main_key]["directory"] = Directory(
                PyFunceble.CONFIGURATION["outputs"][main_key]["directory"]
            ).fix_path()

        for main_key in ["analytic", "logs"]:
            # We loop through the key which are more deeper under the `outputs` index.

            for key, value in PyFunceble.CONFIGURATION["outputs"][main_key][
                "directories"
            ].items():
                # We loop through the more deeper indexes.

                # And we fix the path.
                # Which means: If they do not end with the directory separator, we append
                # it to the end.
                PyFunceble.CONFIGURATION["outputs"][main_key]["directories"][
                    key
                ] = Directory(value).fix_path()

        # We fix the path.
        # Which means: If they do not end with the directory separator, we append
        # it to the end.
        PyFunceble.CONFIGURATION["outputs"]["parent_directory"] = Directory(
            PyFunceble.CONFIGURATION["outputs"]["parent_directory"]
        ).fix_path()

        # We update the STATUS variable with the status from the configuration.
        PyFunceble.STATUS.update(PyFunceble.CONFIGURATION["status"])
        # We update the OUTPUTS variable with the outputs from the configuration.
        PyFunceble.OUTPUTS.update(PyFunceble.CONFIGURATION["outputs"])
        # We update the HTTP_CODE variable with the http_codes from the configuration.
        PyFunceble.HTTP_CODE.update(PyFunceble.CONFIGURATION["http_codes"])
        # We update the LINKS variable with the links from the configuration.
        PyFunceble.LINKS.update(PyFunceble.CONFIGURATION["links"])

        # Those 2 strings are used to say if something like the cleaning went right (done)
        # or wrong (error).
        PyFunceble.INTERN.update(
            {"done": PyFunceble.Fore.GREEN + "✔", "error": PyFunceble.Fore.RED + "✘"}
        )

        # We load the PSL database.
        PublicSuffix().load()

        # We load the IANA database.
        IANA().load()

    @classmethod
    def _set_path_to_configs(cls, path_to_config):
        """
        Set the paths to the configuration files.

        :param path_to_config: The possible path to the config to load.
        :type path_to_config: str

        :return:
            The path to the config to read (0), the path to the default
            configuration to read as fallback.(1)
        :rtype: tuple
        """

        if not path_to_config.endswith(PyFunceble.directory_separator):
            # The path to the config does not ends with the directory separator.

            # We initiate the default and the parsed variable with the directory separator.
            default = parsed = path_to_config + PyFunceble.directory_separator
        else:
            # The path to the config does ends with the directory separator.

            # We initiate the default and the parsed variable.
            default = parsed = path_to_config

        # We append the `CONFIGURATION_FILENAME` to the parsed variable.
        parsed += PyFunceble.CONFIGURATION_FILENAME
        # And we append the `DEFAULT_CONFIGURATION_FILENAME` to the default variable.
        default += PyFunceble.DEFAULT_CONFIGURATION_FILENAME

        # We finaly return a tuple which contain both informations.
        return (parsed, default)

    def _load_config_file(self):
        """
        Load .PyFunceble.yaml into the system.
        """

        try:
            # We try to load the configuration file.

            PyFunceble.CONFIGURATION.update(
                Dict.from_yaml(File(self.path_to_config).read())
            )

            # We install the latest iana configuration file.
            self._install_iana_config()

            # We install the latest public suffix configuration file.
            self._install_psl_config()

            # We install the latest directory structure file.
            self._install_directory_structure_file()
        except FileNotFoundError as exception:
            # But if the configuration file is not found.

            if PyFunceble.path.isfile(self.path_to_default_config):
                # The `DEFAULT_CONFIGURATION_FILENAME` file exists.

                # We copy it as the configuration file.
                File(self.path_to_default_config).copy(self.path_to_config)

                # And we load the configuration file as it does exist (yet).
                self._load_config_file()
            else:
                # The `DEFAULT_CONFIGURATION_FILENAME` file does not exists.

                # We raise the exception we were handling.
                raise exception

    def _install_production_config(self):
        """
        Download the production configuration and install it in the
        current directory.
        """

        # We initiate the link to the production configuration.
        # It is not hard coded because this method is called only if we
        # are sure that the configuration file exist.
        production_config_link = "https://raw.githubusercontent.com/funilrys/PyFunceble/master/.PyFunceble_production.yaml"  # pylint: disable=line-too-long

        # We update the link according to our current version.
        production_config_link = Version(True).right_url_from_version(
            production_config_link
        )

        if not Version(True).is_cloned():
            # The current version is not the cloned one.

            # We download the link content and save it inside the default location.
            #
            # Note: We add this one in order to allow the enduser to always have
            # a copy of our upstream configuration file.
            Download(production_config_link, self.path_to_default_config).text()

        # And we download the link content and return the download status.
        return Download(production_config_link, self.path_to_config).text()

    @classmethod
    def _install_iana_config(cls):
        """
        Download `iana-domains-db.json` if not present.
        """

        # We initiate the link to the iana configuration.
        # It is not hard coded because this method is called only if we
        # are sure that the configuration file exist.
        iana_link = PyFunceble.CONFIGURATION["links"]["iana"]

        # We update the link according to our current version.
        iana_link = Version(True).right_url_from_version(iana_link)

        # We set the destination of the downloaded file.
        destination = PyFunceble.CURRENT_DIRECTORY + "iana-domains-db.json"

        if not Version(True).is_cloned() or not PyFunceble.path.isfile(destination):
            # The current version is not the cloned version.

            # We Download the link content and return the download status.
            return Download(iana_link, destination).text()

        # We are in the cloned version.

        # We do not need to download the file, so we are returning None.
        return None

    @classmethod
    def _install_psl_config(cls):
        """
        Download `public-suffix.json` if not present.
        """

        # We initiate the link to the public suffix configuration.
        # It is not hard coded because this method is called only if we
        # are sure that the configuration file exist.
        psl_link = PyFunceble.CONFIGURATION["links"]["psl"]

        # We update the link according to our current version.
        psl_link = Version(True).right_url_from_version(psl_link)

        # We set the destination of the downloaded file.
        destination = (
            PyFunceble.CURRENT_DIRECTORY
            + PyFunceble.CONFIGURATION["outputs"]["default_files"]["public_suffix"]
        )

        if not Version(True).is_cloned() or not PyFunceble.path.isfile(destination):
            # The current version is not the cloned version.

            # We Download the link content and return the download status.
            return Download(psl_link, destination).text()

        # We are in the cloned version.

        # We do not need to download the file, so we are returning None.
        return None

    @classmethod
    def _install_directory_structure_file(cls):
        """
        Download the latest version of `dir_structure_production.json`.
        """

        # We initiate the link to the public suffix configuration.
        # It is not hard coded because this method is called only if we
        # are sure that the configuration file exist.
        dir_structure_link = PyFunceble.CONFIGURATION["links"]["dir_structure"]

        # We update the link according to our current version.
        dir_structure_link = Version(True).right_url_from_version(dir_structure_link)

        # We set the destination of the downloaded file.
        destination = (
            PyFunceble.CURRENT_DIRECTORY
            + PyFunceble.CONFIGURATION["outputs"]["default_files"]["dir_structure"]
        )

        if not Version(True).is_cloned() or not PyFunceble.path.isfile(destination):
            # The current version is not the cloned version.

            # We Download the link content and return the download status.
            data = Download(dir_structure_link, destination, return_data=True).text()

            File(destination).write(data, overwrite=True)
            return True

        # We are in the cloned version.

        # We do not need to download the file, so we are returning None.
        return None

class Merge:  # pylint: disable=too-few-public-methods
    """
    Merge the old into the new configuration file.

    :param configuration_path: The path to the configuration file to update.
    :type configuration_path: str
    """

    def __init__(self, configuration_path):
        self.path_to_config = configuration_path

        if not self.path_to_config.endswith(PyFunceble.directory_separator):
            self.path_to_config += PyFunceble.directory_separator

        self.path_to_config += PyFunceble.CONFIGURATION_FILENAME

        self.upstream_config = Dict().from_yaml(
            Download(PyFunceble.LINKS["config"], return_data=True).text()
        )

        if self.upstream_config["links"]["config"] != PyFunceble.LINKS["config"]:
            self.upstream_config = Dict().from_yaml(
                Download(self.upstream_config["links"]["repo"], return_data=True).text()
            )

        self.new_config = {}

        self._load()

    def _merge_values(self):
        """
        Simply merge the older into the new one.
        """

        to_remove = []

        self.new_config = Dict(
            Dict(self.upstream_config).merge(PyFunceble.CONFIGURATION)
        ).remove_key(to_remove)

    def _save(self):
        """
        Save the new configuration inside the configuration file.
        """

        Dict(self.new_config).to_yaml(self.path_to_config)

    def _load(self):
        """
        Execute the logic behind the merging.
        """

        if "PYFUNCEBLE_AUTO_CONFIGURATION" not in PyFunceble.environ:
            # The auto configuration environment variable is not set.

            while True:
                # We infinitly loop until we get a reponse which is `y|Y` or `n|N`.

                # We ask the user if we should install and load the default configuration.
                response = input(
                    PyFunceble.Style.BRIGHT
                    + PyFunceble.Fore.RED
                    + "A configuration key is missing.\n"
                    + PyFunceble.Fore.RESET
                    + "Try to merge upstream configuration file into %s ? [y/n] "
                    % (
                        PyFunceble.Style.BRIGHT
                        + self.path_to_config
                        + PyFunceble.Style.RESET_ALL
                    )
                )

                if isinstance(response, str):
                    # The response is a string

                    if response.lower() == "y":
                        # The response is a `y` or `Y`.

                        # We merge the old values inside the new one.
                        self._merge_values()

                        # And we save.
                        self._save()

                        print(
                            PyFunceble.Style.BRIGHT + PyFunceble.Fore.GREEN + "Done!\n"
                            "Please try again, if it happens again,"
                            " please fill a new issue."
                        )

                        # And we break the loop as we got a satisfied response.
                        break

                    elif response.lower() == "n":
                        # The response is a `n` or `N`.

                        # We inform the user that something went wrong.
                        raise Exception("Configuration key still missing.")
        else:
            # The auto configuration environment variable is set.

            # We merge the old values inside the new one.
            self._merge_values()

            # And we save.
            self._save()

class Version:
    """
    Compare the local with the upstream version.

    :param used:
        True: Version is configured for simple usage.
        False: Version compare local with upstream.
    :type used: bool
    """

    def __init__(self, used=False):
        if not used:
            # A method of this class is not called directly.

            # We split the local version.
            self.local_splited = self.split_versions(PyFunceble.VERSION)

            # We initiate the link to the upstream version file.
            # It is hard coded because we may not have the chance to have the
            # configuration file everytime we need it.
            upstream_link = (
                "https://raw.githubusercontent.com/funilrys/PyFunceble/master/version.yaml"
            )  # pylint: disable=line-too-long

            # We update the link according to our current version.
            upstream_link = Version(True).right_url_from_version(upstream_link)

            # We get the link content and convert it to a dict which is more
            # usable.
            self.upstream_data = Dict().from_yaml(
                Download(upstream_link, return_data=True).text()
            )

    @classmethod
    def split_versions(cls, version, return_non_digits=False):
        """
        Convert the versions to a shorter one.

        :param version: The version to split.
        :type version: str

        :param return_non_digits:
            Activate the return of the non-digits parts of the splitted
            version.
        :type return_non_digits: bool

        :return: The splitted version name/numbers.
        :rtype: list
        """

        # We split the version.
        splited_version = version.split(".")

        # We split the parsed version and keep the digits.
        digits = [x for x in splited_version if x.isdigit()]

        if not return_non_digits:
            # We do not have to return the non digits part of the version.

            # We return the digits part of the version.
            return digits

        # We have to return the non digit parts of the version.

        # We split the parsed version and keep the non digits.
        non_digits = [x for x in splited_version if not x.isdigit()]

        # We return a tuple with first the digits part and finally the non digit parts.
        return (digits, non_digits[0])

    @classmethod
    def check_versions_literally(cls, local, upstream):
        """
        Compare the given versions literally.

        :param local: The local version converted by split_versions().
        :type local: str

        :param upstream: The upstream version converted by split_versions().
        :type upstream: str

        :return:
            - True: local == upstream
            - False: local != upstream
        :rtype: bool
        """

        return local == upstream

    @classmethod
    def check_versions(cls, local, upstream):
        """
        Compare the given versions.

        :param local: The local version converted by split_versions().
        :type local: list

        :param upstream: The upstream version converted by split_versions().
        :type upstream: list

        :return:
            - True: local < upstream
            - None: local == upstream
            - False: local > upstream
        :rtype: bool|None
        """

        # A version should be in format [1,2,3] which is actually the version `1.2.3`
        # So as we only have 3 elements in the versioning,
        # we initiate the following variable in order to get the status of each parts.
        status = [None, None, None]

        for index, version_number in enumerate(local):
            # We loop through the local version.

            if int(version_number) < int(upstream[index]):
                # The local version is less than the upstream version.

                # We initiate its status to True which means that we are in
                # an old version (for the current version part).
                status[index] = True
            elif int(version_number) > int(upstream[index]):
                # The local version is greater then the upstream version.

                # We initiate its status to False which means that we are in
                # a more recent version (for the current version part).
                status[index] = False

            # Otherwise the status stay None which means that there is no change
            # between both local and upstream.

        if False in status:
            # There is a False in the status.

            # We return False which means that we are in a more recent version.
            return False

        if True in status:
            # There is a True in the status.

            # We return True which means that we are in a older version.
            return True

        # There is no True or False in the status.

        # We return None which means that we are in the same version as upstream.
        return None

    def compare(self):
        """
        Compare the current version with the upstream saved version.
        """

        if self.upstream_data["force_update"]["status"]:
            # The force_update status is set to True.

            for minimal in self.upstream_data["force_update"]["minimal_version"]:
                # We loop through the list of minimal version which trigger the
                # the force update message.

                # We compare the local with the currently read minimal version.
                checked = self.check_versions(
                    self.local_splited, self.split_versions(minimal)
                )

                if not PyFunceble.CONFIGURATION["quiet"]:
                    # The quiet mode is not activated.

                    if checked or checked is not False and not checked:
                        # The current version is less or equal to
                        # the minimal version.

                        # We initiate the message we are going to return to
                        # the user.
                        message = (
                            PyFunceble.Style.BRIGHT
                            + PyFunceble.Fore.RED
                            + "A critical issue has been fixed.\n"
                            + PyFunceble.Style.RESET_ALL
                        )  # pylint:disable=line-too-long
                        message += (
                            PyFunceble.Style.BRIGHT
                            + PyFunceble.Fore.GREEN
                            + "Please take the time to update PyFunceble!\n"
                            + PyFunceble.Style.RESET_ALL
                        )  # pylint:disable=line-too-long

                        # We print the message on screen.
                        print(message)

                        # We exit PyFunceble with the code 1.
                        exit(1)
                elif checked or checked is not False and not checked:
                    # The quiet mode is activated and the current version
                    # is less or equal to the minimal version.

                    # We raise an exception telling the user to update their
                    # instance of PyFunceble.
                    raise Exception(
                        "A critical issue has been fixed. Please take the time to update PyFunceble!"  # pylint:disable=line-too-long
                    )

        for version in self.upstream_data["deprecated"]:
            # We loop through the list of deprecated versions.

            # We compare the local with the currently read deprecated version.
            checked = self.check_versions(
                self.local_splited, self.split_versions(version)
            )

            if (
                not PyFunceble.CONFIGURATION["quiet"]
                and checked
                or checked is not False
                and not checked
            ):
                # The quiet mode is not activated and the local version is
                # less or equal to the currently read deprecated version.

                # We initiate the message we are going to return to the user.
                message = (
                    PyFunceble.Style.BRIGHT
                    + PyFunceble.Fore.RED
                    + "Your current version is considered as deprecated.\n"
                    + PyFunceble.Style.RESET_ALL
                )  # pylint:disable=line-too-long
                message += (
                    PyFunceble.Style.BRIGHT
                    + PyFunceble.Fore.GREEN
                    + "Please take the time to update PyFunceble!\n"
                    + PyFunceble.Style.RESET_ALL
                )  # pylint:disable=line-too-long

                # We print the message.
                print(message)

                # And we continue to the next logic. There is no need to
                # shutdown PyFunceble as it's just for information.
                return

            # The quiet mode is activated.

            if checked or checked is not False and not checked:
                # The local version is  less or equal to the currently
                # read deprecated version.
                print("Version deprecated.")

                # And we continue to the next logic. There is no need to
                # shutdown PyFunceble as it's just for information.
                return

        # We compare the local version with the upstream version.
        status = self.check_versions(
            self.local_splited,
            self.split_versions(self.upstream_data["current_version"]),
        )

        if status is not None and not status and not PyFunceble.CONFIGURATION["quiet"]:
            # The quiet mode is not activate and the current version is greater than
            # the upstream version.

            # We initiate the message we are going to return to the user.
            message = (
                PyFunceble.Style.BRIGHT
                + PyFunceble.Fore.CYAN
                + "Your version is more recent!\nYou should really think about sharing your changes with the community!\n"  # pylint:disable=line-too-long
                + PyFunceble.Style.RESET_ALL
            )
            message += (
                PyFunceble.Style.BRIGHT
                + "Your version: "
                + PyFunceble.Style.RESET_ALL
                + PyFunceble.VERSION
                + "\n"
            )
            message += (
                PyFunceble.Style.BRIGHT
                + "Upstream version: "
                + PyFunceble.Style.RESET_ALL
                + self.upstream_data["current_version"]
                + "\n"
            )

            # We print the message.
            print(message)
        elif status:
            # The current version is less that the upstream version.

            if not PyFunceble.CONFIGURATION["quiet"]:
                # The quiet mode is not activated.

                # We initiate the message we are going to return to the user.
                message = (
                    PyFunceble.Style.BRIGHT
                    + PyFunceble.Fore.YELLOW
                    + "Please take the time to update PyFunceble!\n"
                    + PyFunceble.Style.RESET_ALL
                )  # pylint:disable=line-too-long
                message += (
                    PyFunceble.Style.BRIGHT
                    + "Your version: "
                    + PyFunceble.Style.RESET_ALL
                    + PyFunceble.VERSION
                    + "\n"
                )  # pylint:disable=line-too-long
                message += (
                    PyFunceble.Style.BRIGHT
                    + "Upstream version: "
                    + PyFunceble.Style.RESET_ALL
                    + self.upstream_data[  # pylint:disable=line-too-long
                        "current_version"
                    ]
                    + "\n"
                )

                # We print the message.
                print(message)
            else:
                # The quiet mode is activated.

                # We print the message.
                print("New version available.")

        # And we continue to the next app logic. There is no need to
        # shutdown PyFunceble as it's just for information.
        return

    @classmethod
    def is_cloned(cls):
        """
        Let us know if we are currently in the cloned version of
        PyFunceble which implicitly mean that we are in developement mode.
        """

        if not PyFunceble.path.isdir(".git"):
            # The git directory does not exist.

            # We return False, the current version is not the cloned version.
            return False

        # We list the list of file which can be found only in a cloned version.
        list_of_file = [
            ".coveragerc",
            ".coveralls.yml",
            ".gitignore",
            ".PyFunceble_production.yaml",
            ".travis.yml",
            "CODE_OF_CONDUCT.md",
            "CONTRIBUTING.md",
            "dir_structure_production.json",
            "MANIFEST.in",
            "README.rst",
            "requirements.txt",
            "setup.py",
            "version.yaml",
        ]

        # We list the list of directory which can be found only in a cloned
        # version.
        list_of_dir = ["docs", "PyFunceble", "tests"]

        for file in list_of_file:
            # We loop through the list of file.

            if not PyFunceble.path.isfile(file):
                # The file does not exist in the current directory.

                # We return False, the current version is not the cloned version.
                return False

        # All required files exist in the current directory.

        for directory in list_of_dir:
            # We loop through the list of directory.

            if not PyFunceble.path.isdir(directory):
                # The directory does not exist in the current directory.

                # We return False, the current version is not the cloned version.
                return False

        # All required directories exist in the current directory.

        # We return True, the current version is a cloned version.
        return True

    @classmethod
    def right_url_from_version(cls, url):
        """
        Convert the GitHub URL to the right one depending of the
        branch or version we are working with.

        :param url: The URL to convert.
        :type url: str

        :return: The converted URL.
        :rtype: str
        """

        if "dev" in PyFunceble.VERSION:
            # `dev` is in the current version.

            # We update and return the url in order to point to the dev branch.
            return url.replace("master", "dev")

        # `dev` is not in the current version.

        # We update and return the url in order to point to the master branch.
        return url.replace("dev", "master")


#!/usr/bin/env python3

# pylint:disable=line-too-long
"""
The tool to check the availability or syntax of domains, IPv4 or URL.

::

    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝

This submodule will create the generation interface/logic.

Author:
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom

Special thanks:
    https://pyfunceble.readthedocs.io/en/master/special-thanks.html

Contributors:
    http://pyfunceble.readthedocs.io/en/master/special-thanks.html

Project link:
    https://github.com/funilrys/PyFunceble

Project documentation:
    https://pyfunceble.readthedocs.io/en/master/

Project homepage:
    https://funilrys.github.io/PyFunceble/

License:
::

    MIT License

    Copyright (c) 2017-2019 Nissar Chababy

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
"""
# pylint: enable=line-too-long
# pylint: disable=bad-continuation, too-many-lines
import PyFunceble
from PyFunceble import directory_separator
from PyFunceble.database import Inactive
from PyFunceble.percentage import Percentage
from PyFunceble.prints import Prints

class Generate:  # pragma: no cover pylint:disable=too-many-instance-attributes
    """
    Generate different sort of files.

    :param domain_status: The domain status.
    :type domain_status: str

    :param source: The source of the given status.
    :type source: str

    :param expiration_date: The expiration date of the domain (if catched).
    :type expiration_date: str
    """

    def __init__(self, domain_status, source=None, expiration_date=None):
        # We get the domain status.
        self.domain_status = domain_status

        # We get the source.
        self.source = source

        if not expiration_date:
            # The expiration date is not give or is empty.

            # We set the expiration date.
            self.expiration_date = "Unknown"
        else:
            # The expiration date is given.

            # We get the expiration date.
            self.expiration_date = expiration_date

        # We construct the output parent directory.
        self.output_parent_dir = (
            PyFunceble.OUTPUT_DIRECTORY + PyFunceble.OUTPUTS["parent_directory"]
        )

        if "to_test" in PyFunceble.INTERN and PyFunceble.INTERN["to_test"]:
            # We are testing something.

            # We save it into an unified variable.
            self.tested = PyFunceble.INTERN["to_test"]

        if PyFunceble.CONFIGURATION["user_agent"]:
            # The user-agent (from the configuration file) is not empty.

            # We initiate the header to use with our request.
            self.headers = {"User-Agent": PyFunceble.CONFIGURATION["user_agent"]}
        else:
            # The user-agent (from the configuration file) is empty.

            # We initiate an empty header to use with our request.
            self.headers = {}

        # We handle possible non existant index.
        self._handle_non_existant_index()

    @classmethod
    def _handle_non_existant_index(cls):
        """
        Handle and check that some configuration index exists.
        """

        try:
            # We try to call the http code.
            PyFunceble.INTERN["http_code"]
        except KeyError:
            # If it is not found.

            # We initiate an empty http code.
            PyFunceble.INTERN["http_code"] = "*" * 3

        try:
            # We try to call the referer.
            PyFunceble.INTERN["referer"]
        except KeyError:
            # If it is not found.

            # We initate an `Unknown` referer.
            PyFunceble.INTERN["referer"] = "Unknown"

    def _analytic_host_file_directory(self):
        """
        Return the analytic directory to write depending of the matched
        status.
        """

        # We construct the path to the analytic directory.
        output_dir = (
            self.output_parent_dir
            + PyFunceble.OUTPUTS["analytic"]["directories"]["parent"]
        )

        if self.domain_status.lower() in PyFunceble.STATUS["list"]["potentially_up"]:
            # The status is in the list of analytic up status.

            # We complete the output directory.
            output_dir += PyFunceble.OUTPUTS["analytic"]["directories"][
                "potentially_up"
            ]
        elif (
            self.domain_status.lower() in PyFunceble.STATUS["list"]["potentially_down"]
        ):
            # The status is in the list of analytic down status.

            # We complete the output directory.
            output_dir += PyFunceble.OUTPUTS["analytic"]["directories"][
                "potentially_down"
            ]
        elif self.domain_status.lower() in PyFunceble.STATUS["list"]["suspicious"]:
            # The status is in the list of analytic suspicious status.

            # We complete the output directory.
            output_dir += PyFunceble.OUTPUTS["analytic"]["directories"]["suspicious"]
        else:
            # The status is not in the list of analytic down or up status.

            # We complete the output directory.
            output_dir += PyFunceble.OUTPUTS["analytic"]["directories"]["up"]

        return output_dir

    def info_files(self):  # pylint: disable=inconsistent-return-statements
        """
        Generate the hosts file, the plain list and the splitted lists.
        """

        if self._do_not_produce_file():
            # We do not have to produce file.

            # We return false.
            return False

        if (
            "file_to_test" in PyFunceble.INTERN
            and PyFunceble.INTERN["file_to_test"]
            and (
                PyFunceble.CONFIGURATION["generate_hosts"]
                or PyFunceble.CONFIGURATION["plain_list_domain"]
                or PyFunceble.CONFIGURATION["generate_json"]
            )
        ):
            # * We are not testing as an imported module.
            # and
            # * The hosts file generation is activated.
            # or
            # * The plain list generation is activated.

            # We initiate a variable which whill save the splited testination.
            splited_destination = ""

            # We initiate the list of all analytic related statuses.
            http_list = []
            http_list.extend(PyFunceble.STATUS["list"]["potentially_up"])
            http_list.extend(PyFunceble.STATUS["list"]["potentially_down"])
            http_list.extend(PyFunceble.STATUS["list"]["http_active"])
            http_list.extend(PyFunceble.STATUS["list"]["suspicious"])

            # We partially initiate the path to the hosts file.
            output_hosts = (
                self.output_parent_dir
                + PyFunceble.OUTPUTS["hosts"]["directory"]
                + "%s"
                + directory_separator
                + PyFunceble.OUTPUTS["hosts"]["filename"]
            )

            # We partially initiate the path to the plain list file.
            output_domains = (
                self.output_parent_dir
                + PyFunceble.OUTPUTS["domains"]["directory"]
                + "%s"
                + directory_separator
                + PyFunceble.OUTPUTS["domains"]["filename"]
            )

            # We partially intiate the path to the json list file.
            output_json = (
                self.output_parent_dir
                + PyFunceble.OUTPUTS["json"]["directory"]
                + "%s"
                + directory_separator
                + PyFunceble.OUTPUTS["json"]["filename"]
            )

            if self.domain_status.lower() in PyFunceble.STATUS["list"]["up"]:
                # The status is in the list of up list.

                # We complete the path to the hosts file.
                hosts_destination = output_hosts % PyFunceble.STATUS["official"]["up"]

                # We complete the path to the plain list file.
                plain_destination = output_domains % PyFunceble.STATUS["official"]["up"]

                # We complete the path to the json list file.
                json_destination = output_json % PyFunceble.STATUS["official"]["up"]
            elif self.domain_status.lower() in PyFunceble.STATUS["list"]["valid"]:
                # The status is in the list of valid list.

                # We complete the path to the hosts file.
                hosts_destination = (
                    output_hosts % PyFunceble.STATUS["official"]["valid"]
                )

                # We complete the path to the plain list file.
                plain_destination = (
                    output_domains % PyFunceble.STATUS["official"]["valid"]
                )

                # We complete the path to the json list file.
                json_destination = output_json % PyFunceble.STATUS["official"]["valid"]
            elif self.domain_status.lower() in PyFunceble.STATUS["list"]["down"]:
                # The status is in the list of down list.

                # We complete the path to the hosts file.
                hosts_destination = output_hosts % PyFunceble.STATUS["official"]["down"]

                # We complete the path to the plain list file.
                plain_destination = (
                    output_domains % PyFunceble.STATUS["official"]["down"]
                )

                # We complete the path to the json list file.
                json_destination = output_json % PyFunceble.STATUS["official"]["down"]
            elif self.domain_status.lower() in PyFunceble.STATUS["list"]["invalid"]:
                # The status is in the list of invalid list.

                # We complete the path to the hosts file.
                hosts_destination = (
                    output_hosts % PyFunceble.STATUS["official"]["invalid"]
                )

                # We complete the path to the plain list file.
                plain_destination = (
                    output_domains % PyFunceble.STATUS["official"]["invalid"]
                )

                # We complete the path to the json list file.
                json_destination = (
                    output_json % PyFunceble.STATUS["official"]["invalid"]
                )
            elif self.domain_status.lower() in http_list:
                # The status is in the list of analytic status.

                # We construct the path to the analytic directory.
                output_dir = self._analytic_host_file_directory()

                if not output_dir.endswith(directory_separator):
                    # The output directory does not ends with the directory separator.

                    # We append the directory separator at the end of the output directory.
                    output_dir += directory_separator

                # We initiate the hosts file path.
                hosts_destination = output_dir + PyFunceble.OUTPUTS["hosts"]["filename"]

                # We initiate the plain list file path.
                plain_destination = (
                    output_dir + PyFunceble.OUTPUTS["domains"]["filename"]
                )

                # We complete the path to the json list file.
                json_destination = output_dir + PyFunceble.OUTPUTS["json"]["filename"]

                # We initiate the path to the http code file.
                # Note: We generate the http code file so that
                # we can have each domain in a file which is the
                # extracted http code.
                splited_destination = output_dir + str(PyFunceble.INTERN["http_code"])

            if PyFunceble.CONFIGURATION["generate_hosts"]:
                # The hosts file generation is activated.

                # We generate/append the currently tested element in its
                # final location. (hosts file format)
                # We print on screen and on file.
                Prints(
                    [PyFunceble.CONFIGURATION["custom_ip"], self.tested],
                    "FullHosts",
                    hosts_destination,
                ).data()

            if PyFunceble.CONFIGURATION["plain_list_domain"]:
                # The plain list generation is activated.

                # We generate/append the currently tested element in its
                # final location. (the plain list format)
                # We print on file.
                Prints([self.tested], "PlainDomain", plain_destination).data()

            if PyFunceble.CONFIGURATION["split"] and splited_destination:
                # The splited list generation is activated.

                # We generate/append the currently tested element in its
                # final location. (the split list format)
                # We print on file.
                Prints([self.tested], "PlainDomain", splited_destination).data()

            if PyFunceble.CONFIGURATION["generate_json"]:
                # The jsaon list generation is activated.

                # We generate/append the currently tested element in its
                # final location. (the json format)
                # We print on file.
                Prints([self.tested], "JSON", json_destination).data()

    def unified_file(self):
        """
        Generate unified file. Understand by that that we use an unified table
        instead of a separate table for each status which could result into a
        misunderstanding.
        """

        if (
            "file_to_test" in PyFunceble.INTERN
            and PyFunceble.INTERN["file_to_test"]
            and PyFunceble.CONFIGURATION["unified"]
        ):
            # * We are not testing as an imported module.
            # and
            # * The unified file generation is activated.

            # We construct the path of the unified file.
            output = (
                self.output_parent_dir + PyFunceble.OUTPUTS["default_files"]["results"]
            )

            if PyFunceble.CONFIGURATION["less"]:
                # We have to print less information.

                if PyFunceble.HTTP_CODE["active"]:
                    # The http status code request is activated.

                    # We construct what we have to print.
                    to_print = [
                        self.tested,
                        self.domain_status,
                        PyFunceble.INTERN["http_code"],
                    ]
                else:
                    # The http status code request is not activated.

                    # We construct what we have to print.
                    to_print = [self.tested, self.domain_status, self.source]

                # And we print the informations on file.
                Prints(to_print, "Less", output, True).data()
            else:
                # The unified file generation is not activated.

                # We construct what we have to print.
                to_print = [
                    self.tested,
                    self.domain_status,
                    self.expiration_date,
                    self.source,
                    PyFunceble.INTERN["http_code"],
                    PyFunceble.CURRENT_TIME,
                ]

                # And we print the information on file.
                Prints(to_print, "Generic_File", output, True).data()

    def analytic_file(self, new_status, old_status=None):
        """
        Generate :code:`Analytic/*` files based on the given old and
        new statuses.

        :param new_status: The new status of the domain.
        :type new_status: str

        :param old_status: The old status of the domain.
        :type old_status: str
        """

        if not old_status:
            # The old status is not given.

            # We set the old status as the one given globally.
            old_status = self.domain_status

        if "file_to_test" in PyFunceble.INTERN and PyFunceble.INTERN["file_to_test"]:
            # We are not testing as an imported module.

            # We partially construct the path to the file to write/print.
            output = (
                self.output_parent_dir
                + PyFunceble.OUTPUTS["analytic"]["directories"]["parent"]
                + "%s%s"
            )

            if new_status.lower() in PyFunceble.STATUS["list"]["up"]:
                # The new status is in the list of up status.

                # We complete the output directory.
                output = output % (
                    PyFunceble.OUTPUTS["analytic"]["directories"]["up"],
                    PyFunceble.OUTPUTS["analytic"]["filenames"]["up"],
                )

                # We generate the hosts file.
                Generate("HTTP_Active").info_files()
            elif new_status.lower() in PyFunceble.STATUS["list"]["potentially_up"]:
                # The new status is in the list of down status.

                # We complete the output directory.
                output = output % (
                    PyFunceble.OUTPUTS["analytic"]["directories"]["potentially_up"],
                    PyFunceble.OUTPUTS["analytic"]["filenames"]["potentially_up"],
                )

                # We generate the hosts file.
                Generate("potentially_up").info_files()
            elif new_status.lower() in PyFunceble.STATUS["list"]["suspicious"]:
                # The new status is in the list of suspicious status.

                # We complete the output directory.
                output = output % (
                    PyFunceble.OUTPUTS["analytic"]["directories"]["suspicious"],
                    PyFunceble.OUTPUTS["analytic"]["filenames"]["suspicious"],
                )

                # We generate the hosts file.
                Generate("suspicious").info_files()
            else:
                # The new status is in the list of up and down status.

                # We complete the output directory.
                output = output % (
                    PyFunceble.OUTPUTS["analytic"]["directories"]["potentially_down"],
                    PyFunceble.OUTPUTS["analytic"]["filenames"]["potentially_down"],
                )

                # We generate the hosts files.
                Generate("potentially_down").info_files()

            # We print the information on file.
            Prints(
                [
                    self.tested,
                    old_status,
                    PyFunceble.INTERN["http_code"],
                    PyFunceble.CURRENT_TIME,
                ],
                "HTTP",
                output,
                True,
            ).data()

    def _prints_status_file(self):  # pylint: disable=too-many-branches
        """
        Logic behind the printing (in file) when generating status file.
        """

        if PyFunceble.INTERN["file_to_test"]:
            # We are testing a file.

            output = (
                self.output_parent_dir
                + PyFunceble.OUTPUTS["splited"]["directory"]
                + self.domain_status
            )

            if PyFunceble.CONFIGURATION["less"]:
                # We have to print less information.

                # We print the information on file.
                Prints(
                    [self.tested, self.domain_status, self.source], "Less", output, True
                ).data()
            elif PyFunceble.CONFIGURATION["split"]:
                # We have to split the information we print on file.

                if self.domain_status.lower() in PyFunceble.STATUS["list"]["up"]:
                    # The status is in the list of up status.

                    if PyFunceble.HTTP_CODE["active"]:
                        # The http code extraction is activated.

                        # We initiate the data to print.
                        data_to_print = [
                            self.tested,
                            self.expiration_date,
                            self.source,
                            PyFunceble.INTERN["http_code"],
                            PyFunceble.CURRENT_TIME,
                        ]
                    else:
                        # The http code extraction is not activated.

                        # We initiate the data to print.
                        data_to_print = [
                            self.tested,
                            self.expiration_date,
                            self.source,
                            PyFunceble.CURRENT_TIME,
                        ]

                    # We print the informations to print on file.
                    Prints(
                        data_to_print, PyFunceble.STATUS["official"]["up"], output, True
                    ).data()
                elif self.domain_status.lower() in PyFunceble.STATUS["list"]["valid"]:
                    # The status is in the list of valid status.

                    # We initiate the data to print.
                    data_to_print = [self.tested, self.source, PyFunceble.CURRENT_TIME]

                    # We print the informations to print on file.
                    Prints(
                        data_to_print,
                        PyFunceble.STATUS["official"]["valid"],
                        output,
                        True,
                    ).data()
                elif self.domain_status.lower() in PyFunceble.STATUS["list"]["down"]:
                    # The status is in the list of down status.

                    if PyFunceble.HTTP_CODE["active"]:
                        # The http statuc code extraction is activated.

                        # We initiate the data to print.
                        data_to_print = [
                            self.tested,
                            PyFunceble.INTERN["referer"],
                            self.domain_status,
                            self.source,
                            PyFunceble.INTERN["http_code"],
                            PyFunceble.CURRENT_TIME,
                        ]
                    else:
                        # The http status code extraction is not activated.

                        # We initate the data to print.
                        data_to_print = [
                            self.tested,
                            PyFunceble.INTERN["referer"],
                            self.domain_status,
                            self.source,
                            PyFunceble.CURRENT_TIME,
                        ]

                    # We print the information on file.
                    Prints(
                        data_to_print,
                        PyFunceble.STATUS["official"]["down"],
                        output,
                        True,
                    ).data()
                elif self.domain_status.lower() in PyFunceble.STATUS["list"]["invalid"]:
                    # The status is in the list of invalid status.

                    if PyFunceble.HTTP_CODE["active"]:
                        # The http status code extraction is activated.

                        # We initiate the data to print.
                        data_to_print = [
                            self.tested,
                            self.source,
                            PyFunceble.INTERN["http_code"],
                            PyFunceble.CURRENT_TIME,
                        ]
                    else:
                        # The http status code extraction is not activated.

                        # We initiate the data to print.
                        data_to_print = [
                            self.tested,
                            self.source,
                            PyFunceble.CURRENT_TIME,
                        ]

                    # We print the information to print on file.
                    Prints(
                        data_to_print,
                        PyFunceble.STATUS["official"]["invalid"],
                        output,
                        True,
                    ).data()

    def _prints_status_screen(self):
        """
        Logic behind the printing (on screen) when generating status file.
        """

        if not PyFunceble.CONFIGURATION["quiet"]:
            # The quiet mode is not activated.

            if PyFunceble.CONFIGURATION["less"]:
                # We have to print less information.

                # We initiate the data to print.
                to_print = [
                    self.tested,
                    self.domain_status,
                    PyFunceble.INTERN["http_code"],
                ]

                if not PyFunceble.HTTP_CODE["active"]:
                    # The http status code is not activated.

                    # We replace the last element to print with
                    # the source.
                    to_print[-1] = self.source

                # We print the informations on screen.
                Prints(to_print, "Less").data()
            else:
                # We have to print all informations on screen.

                if PyFunceble.HTTP_CODE["active"]:
                    # The http status code extraction is activated.

                    # We initiate the data to print.
                    data_to_print = [
                        self.tested,
                        self.domain_status,
                        self.expiration_date,
                        self.source,
                        PyFunceble.INTERN["http_code"],
                    ]
                else:
                    # The http status code extraction is not activated.

                    # We initiate the data to print.
                    data_to_print = [
                        self.tested,
                        self.domain_status,
                        self.expiration_date,
                        self.source,
                        PyFunceble.CURRENT_TIME,
                    ]

                # We print the information on screen.
                Prints(data_to_print, "Generic").data()

    def status_file(self):  # pylint: disable=inconsistent-return-statements
        """
        Generate a file according to the domain status.
        """

        if "file_to_test" in PyFunceble.INTERN:
            # We are not testing as an imported module.

            # We generate the hosts file.
            Generate(self.domain_status, self.source, self.expiration_date).info_files()

            # We are testing a file content.

            # We increase the percentage count.
            Percentage(self.domain_status).count()

            # We print on screen if needed.
            self._prints_status_screen()

            if self._do_not_produce_file():
                return None

            if (
                not PyFunceble.CONFIGURATION["no_files"]
                and PyFunceble.CONFIGURATION["split"]
            ):
                # * The file non-generation of file is globaly deactivated.
                # and
                # * We have to split the outputs.

                # We print or generate the files.
                self._prints_status_file()
            else:
                # * The file non-generation of file is globaly activated.
                # or
                # * We do not have to split the outputs.

                # We print or generate the unified files.
                self.unified_file()

    def _do_not_produce_file(self):
        """
        Check if we are allowed to produce a file based from the given
        information.

        :return:
            The state of the production.
            True: We do not produce file.
            False: We do produce file.
        :rtype: bool
        """

        if (
            Inactive().is_present()
            and self.domain_status
            in [
                PyFunceble.STATUS["official"]["down"],
                PyFunceble.STATUS["official"]["invalid"],
            ]
            and PyFunceble.INTERN["to_test"]
            not in PyFunceble.INTERN["extracted_list_to_test"]
        ):
            return True
        return False


#!/usr/bin/env python3

# pylint:disable=line-too-long
"""
The tool to check the availability or syntax of domains, IPv4 or URL.

::

    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝

This submodule will provide us a place and way to intertact with the helpful public sufix database.

Author:
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom

Special thanks:
    https://pyfunceble.readthedocs.io/en/master/special-thanks.html

Contributors:
    http://pyfunceble.readthedocs.io/en/master/special-thanks.html

Project link:
    https://github.com/funilrys/PyFunceble

Project documentation:
    https://pyfunceble.readthedocs.io/en/master/

Project homepage:
    https://funilrys.github.io/PyFunceble/

License:
::

    MIT License

    Copyright (c) 2017-2019 Nissar Chababy

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
"""
# pylint:enable=line-too-long
# pylint:disable=bad-continuation
import PyFunceble
from PyFunceble.helpers import Dict, Download, File, List

class PublicSuffix:  # pragma: no cover pylint: disable=too-few-public-methods
    """
    Let us interact with the public suffix database.
    """

    def __init__(self):
        # We initiate the destination of our database.
        self.destination = (
            PyFunceble.CURRENT_DIRECTORY
            + PyFunceble.OUTPUTS["default_files"]["public_suffix"]
        )

        # We initiate a variablw which will save the database we are going to save.
        self.public_suffix_db = {}

        if "psl_db" not in PyFunceble.INTERN:
            # The psl database was not initiated.

            PyFunceble.INTERN["psl_db"] = {}

    @classmethod
    def _data(cls):
        """
        Get the database from the public suffix repository.
        """

        # We initiate a variable which will save the link to the upstream public suffix file.
        public_suffix_url = (
            "https://raw.githubusercontent.com/publicsuffix/list/%s/public_suffix_list.dat"
            % "master"
        )

        # And we return the content of the previously declared link.
        return Download(public_suffix_url, return_data=True).text()

    def _extensions(self, line):
        """
        Extract the extension from the given line.

        :param line: The line from the official public suffix repository.
        :type line: str
        """

        # We strip the parsed line.
        line = line.strip()

        if not line.startswith("//") and "." in line:
            # * The parsed line is not a commented line.
            # and
            # * There is a point in the parsed line.
            line = line.encode("idna").decode("utf-8")

            if line.startswith("*."):
                # The parsed line start with `*.`.

                # We remove the first two characters.
                line = line[2:]

            # We we split the points and we get the last element.
            # Explanation: The idea behind this action is to
            # always get the extension.
            extension = line.split(".")[-1]

            if extension in self.public_suffix_db:
                # The extension is alrady in our database.

                # We update the content of the 1st level TDL with
                # the content of the suffix.
                # In between, we format so that we ensure that there is no
                # duplicate in the database index content.
                self.public_suffix_db[extension] = List(
                    self.public_suffix_db[extension] + [line]
                ).format()
            else:
                # The extension is not already in our database.

                # We append the currently formatted extension and the line content.
                self.public_suffix_db.update({extension: [line]})

    def update(self):
        """
        Update of the content of the :code:`public-suffix.json`.
        """

        if not PyFunceble.CONFIGURATION["quiet"]:
            # The quiet mode is not activated.

            # We print a message for the user on screen.
            print(
                "Update of %s" % PyFunceble.OUTPUTS["default_files"]["public_suffix"],
                end=" ",
            )

        # We loop through the line of the upstream file.
        list(map(self._extensions, self._data().split("\n")))

        # We save the content of our database in the final testination.
        Dict(self.public_suffix_db).to_json(self.destination)

        if not PyFunceble.CONFIGURATION["quiet"]:
            # The quiet mode is not activated.

            # We inform the user that everything goes right.
            print(PyFunceble.INTERN["done"])

    def load(self):
        """
        Load the public suffix database into the system.
        """

        if not PyFunceble.INTERN["psl_db"]:
            # The public database was not already loaded.

            # * We read, convert to dict and return the file content.
            # and
            # * We fill/create the database.
            PyFunceble.INTERN["psl_db"] = Dict().from_json(
                File(self.destination).read()
            )


# pylint:disable=line-too-long
"""
The tool to check the availability or syntax of domains, IPv4 or URL.

::

    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝

This submodule will provide some sorting presets.

Author:
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom

Special thanks:
    https://pyfunceble.readthedocs.io/en/master/special-thanks.html

Contributors:
    http://pyfunceble.readthedocs.io/en/master/special-thanks.html

Project link:
    https://github.com/funilrys/PyFunceble

Project documentation:
    https://pyfunceble.readthedocs.io/en/master/

Project homepage:
    https://funilrys.github.io/PyFunceble/

License:
::

    MIT License

    Copyright (c) 2017-2019 Nissar Chababy

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
"""
# pylint: enable=line-too-long

import PyFunceble
from PyFunceble.check import Check
from PyFunceble.helpers import Regex

class Sort:  # pylint: disable=too-few-public-methods
    """
    Provide some sorting presets which we can
    parse to PyFunceble.helpers.List().custom_format().
    """

    # We initiate a regex which will match everything which is not
    # a letter, a number or a point.
    regex_replace = r"[^a-zA-Z0-9]"

    @classmethod
    def standard(cls, element):
        """
        Implement the standard and alphabetical sorting.

        :param element: The element we are currently reading.
        :type element: str

        :return: The formatted element.
        :rtype: str
        """

        # We remove all special characters and return the formatted string.
        return (
            Regex(element, cls.regex_replace, replace_with="@funilrys")
            .replace()
            .replace("@funilrys", "")
        )

    @classmethod
    def hierarchical(cls, element):
        """
        The idea behind this method is to sort a list of domain hierarchicaly.

        :param element: The element we are currently reading.
        :type element: str

        :return: The formatted element.
        :rtype: str

        .. note::
            For a domain like :code:`aaa.bbb.ccc.tdl`.

            A normal sorting is done in the following order:
                1. :code:`aaa`
                2. :code:`bbb`
                3. :code:`ccc`
                4. :code:`tdl`

            This method allow the sorting to be done in the following order:
                1. :code:`tdl`
                2. :code:`ccc`
                3. :code:`bbb`
                4. :code:`aaa`

        """

        # We initiate a variable which will save the element to sort without
        # the extension.
        to_sort = ""

        # We initiate a variable which will save the full extension.
        full_extension = ""

        # We convert the parsed element to lower case.
        element = element.lower()

        # We try to get the url base.
        url_base = Check().is_url_valid(element, return_base=True)

        if not isinstance(url_base, str):
            # The url base is not found.

            if "." in element:
                # There is point in the parsed element.

                # We get the position of the first letter of the extension.
                extension_index = element.rindex(".") + 1

                # We get the extension from the position of the first letter
                # of the extension.
                extension = element[extension_index:]

                if extension in PyFunceble.INTERN["psl_db"]:
                    # The extension is in the public suffix database.

                    for suffix in PyFunceble.INTERN["psl_db"][extension]:
                        # We loop through the list of suffix of the extracted extension.

                        # We suffix the sufix with a point.
                        formatted_suffix = "." + suffix

                        if element.endswith(formatted_suffix):
                            # The elements ends with the suffix.

                            # We get the position of the first character of the suffix in
                            # the parsed element.
                            suffix_index = element.rindex(formatted_suffix)

                            # We update the to_sort variable with the element without the suffix.
                            to_sort = element[:suffix_index]

                            # We replace the full extension with the currently read suffix.
                            full_extension = suffix

                            # We break the loop, we got what we wanted.
                            break

                if not full_extension:
                    # The full extension is empty.

                    # We initiate it with the extension.
                    full_extension = element[extension_index:]

                    # We update the to_sort variable with the element without the extension.
                    to_sort = element[: extension_index - 1]

                # We append a point to the full extension because the point has to be
                # at the end and not at the begining of the extension.
                # To understand: Imagine a miror.
                full_extension += "."

                # We reverse the to_sort string.
                tros_ot = to_sort[::-1]

                if "." in tros_ot:
                    # There is a point in the reversed string.

                    # We prefix the full extension with the top level
                    # domain name.
                    full_extension = (
                        tros_ot[: tros_ot.index(".")][::-1] + "." + full_extension
                    )

                    # We remove the tor level domain from the rest of
                    # the reversed string.
                    tros_ot = tros_ot[tros_ot.index(".") + 1 :]

                    # * We reverse each level of the parsed element.
                    # and
                    # * We glue each level of the parsed element with each other.
                    #
                    # Note: after this, there is no point anymore.
                    reversion = full_extension + ".".join(
                        [x[::-1] for x in tros_ot.split(".")]
                    )

                    # We remove all special characters and return the formatted string.
                    return (
                        Regex(reversion, cls.regex_replace, replace_with="@funilrys")
                        .replace()
                        .replace("@funilrys", "")
                    )

                # We remove all special characters and return the formatted string.
                return (
                    Regex(
                        to_sort + full_extension,
                        cls.regex_replace,
                        replace_with="@funilrys",
                    )
                    .replace()
                    .replace("@funilrys", "")
                )

            # There is no point in the parsed element.

            # We return the parsed element.
            return element

        # The url base is found.

        # We get the position of the element.
        protocol_position = element.rindex(url_base)

        # We extract the protocol from the element position.
        protocol = element[:protocol_position]

        # We return the output of this method but with the url base instead of the full url.
        return protocol + cls.hierarchical(url_base)


#!/usr/bin/env python3

# pylint:disable=line-too-long
"""
The tool to check the availability or syntax of domains, IPv4 or URL.

::

    ██████╗ ██╗   ██╗███████╗██╗   ██╗███╗   ██╗ ██████╗███████╗██████╗ ██╗     ███████╗
    ██╔══██╗╚██╗ ██╔╝██╔════╝██║   ██║████╗  ██║██╔════╝██╔════╝██╔══██╗██║     ██╔════╝
    ██████╔╝ ╚████╔╝ █████╗  ██║   ██║██╔██╗ ██║██║     █████╗  ██████╔╝██║     █████╗
    ██╔═══╝   ╚██╔╝  ██╔══╝  ██║   ██║██║╚██╗██║██║     ██╔══╝  ██╔══██╗██║     ██╔══╝
    ██║        ██║   ██║     ╚██████╔╝██║ ╚████║╚██████╗███████╗██████╔╝███████╗███████╗
    ╚═╝        ╚═╝   ╚═╝      ╚═════╝ ╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═════╝ ╚══════╝╚══════╝

This submodule will provide the IANA logic and interface.

Author:
    Nissar Chababy, @funilrys, contactTATAfunilrysTODTODcom

Special thanks:
    https://pyfunceble.readthedocs.io/en/master/special-thanks.html

Contributors:
    http://pyfunceble.readthedocs.io/en/master/special-thanks.html

Project link:
    https://github.com/funilrys/PyFunceble

Project documentation:
    https://pyfunceble.readthedocs.io/en/master/

Project homepage:
    https://funilrys.github.io/PyFunceble/

License:
::

    MIT License

    Copyright (c) 2017-2019 Nissar Chababy

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
    copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    SOFTWARE.
"""
# pylint: enable=line-too-long
# pylint: disable=bad-continuation
import PyFunceble
from PyFunceble.helpers import Dict, Download, File, Regex
from PyFunceble.lookup import Lookup

class IANA:  # pragma: no cover pylint: disable=too-few-public-methods
    """
    Logic behind the update and usage of `iana-domains-db.json`
    """

    def __init__(self):
        # We get the destination of the constructed IANA database.
        self.destination = (
            PyFunceble.CURRENT_DIRECTORY + PyFunceble.OUTPUTS["default_files"]["iana"]
        )

        if PyFunceble.path.isfile(self.destination):
            # The destination exist.

            # We get its content.
            self.iana_db = Dict().from_json(File(self.destination).read())
        else:
            # The destination does not exist.

            # We initiate the local variable which will save the content of the database.
            self.iana_db = {}

        # We initiate the URL to the IANA Root Zone Database page.
        self.iana_url = "https://www.iana.org/domains/root/db"

        # We iniitate an instance of Lookup.
        self.lookup = Lookup()

        # We map the list of server which have to be set manually because
        # they are not present into the IANA Root Zone Database.
        self.manual_server = {
            "aaa": "whois.nic.aaa",
            "abb": "whois.nic.abb",
            "able": "whois.nic.able",
            "accenture": "whois.nic.accenture",
            "aetna": "whois.nic.aetna",
            "aig": "whois.nic.aig",
            "americanexpress": "whois.nic.americanexpress",
            "amex": "whois.nic.amex",
            "amica": "whois.nic.amica",
            "amsterdam": "whois.nic.amsterdam",
            "analytics": "whois.nic.analytics",
            "aramco": "whois.nic.aramco",
            "arte": "whois.nic.arte",
            "as": "whois.nic.as",
            "athleta": "whois.nic.athleta",
            "audible": "whois.nic.audible",
            "author": "whois.nic.author",
            "aws": "whois.nic.aws",
            "axa": "whois.nic.axa",
            "azure": "whois.nic.azure",
            "baby": "whois.nic.baby",
            "banamex": "whois.nic.banamex",
            "bananarepublic": "whois.nic.bananarepublic",
            "baseball": "whois.nic.baseball",
            "bharti": "whois.nic.bharti",
            "bing": "whois.nic.bing",
            "bloomberg": "whois.nic.bloomberg",
            "bm": "whois.afilias-srs.net",
            "book": "whois.nic.book",
            "booking": "whois.nic.booking",
            "bot": "whois.nic.bot",
            "buzz": "whois.nic.buzz",
            "bz": "whois.afilias-grs.net",
            "call": "whois.nic.call",
            "calvinklein": "whois.nic.calvinklein",
            "caravan": "whois.nic.caravan",
            "cartier": "whois.nic.cartier",
            "caseih": "whois.nic.caseih",
            "cbn": "whois.nic.cbn",
            "cbre": "whois.nic.cbre",
            "cd": "chois.nic.cd",
            "chase": "whois.nic.chase",
            "circle": "whois.nic.circle",
            "cisco": "whois.nic.cisco",
            "citadel": "whois.nic.citadel",
            "citi": "whois.nic.citi",
            "citic": "whois.nic.citic",
            "cm": "whois.netcom.cm",
            "coupon": "whois.nic.coupon",
            "crown": "whois.nic.crown",
            "crs": "whois.nic.crs",
            "deal": "whois.nic.deal",
            "dealer": "whois.nic.dealer",
            "dell": "whois.nic.dell",
            "dhl": "whois.nic.dhl",
            "discover": "whois.nic.discover",
            "dnp": "whois.nic.dnp",
            "doosan": "whois.nic.doosan",
            "duns": "whois.nic.duns",
            "dupont": "whois.nic.dupont",
            "earth": "whois.nic.earth",
            "energy": "whois.nic.energy",
            "epost": "whois.nic.epost",
            "everbank": "whois.nic.everbank",
            "farmers": "whois.nic.farmers",
            "fast": "whois.nic.fast",
            "ferrero": "whois.nic.ferrero",
            "fire": "whois.nic.fire",
            "fj": "whois.usp.ac.fj",
            "flickr": "whois.nic.flickr",
            "flir": "whois.nic.flir",
            "food": "whois.nic.food",
            "ford": "whois.nic.ford",
            "fox": "whois.nic.fox",
            "free": "whois.nic.free",
            "frontier": "whois.nic.frontier",
            "ftr": "whois.nic.ftr",
            "ga": "whois.my.ga",
            "gap": "whois.nic.gap",
            "gh": "whois.nic.gh",
            "gmo": "whois.nic.gmo",
            "got": "whois.nic.got",
            "grainger": "whois.nic.grainger",
            "grocery": "whois.nic.grocery",
            "guardian": "whois.nic.guardian",
            "gucci": "whois.nic.gucci",
            "hair": "whois.nic.hair",
            "hbo": "whois.nic.hbo",
            "health": "whois.nic.health",
            "homegoods": "whois.nic.homegoods",
            "homesense": "whois.nic.homesense",
            "honeywell": "whois.nic.honeywell",
            "hot": "whois.nic.hot",
            "hoteles": "whois.nic.hoteles",
            "hotels": "whois.nic.hotels",
            "hotmail": "whois.nic.hotmail",
            "hsbc": "whois.nic.hsbc",
            "htc": "whois.nic.htc",
            "hyatt": "whois.nic.hyatt",
            "ieee": "whois.nic.ieee",
            "iinet": "whois.nic.iinet",
            "imdb": "whois.nic.imdb",
            "int": "whois.iana.org",
            "intel": "whois.nic.intel",
            "intuit": "whois.nic.intuit",
            "ipiranga": " whois.nic.ipiranga",
            "ipirange": "whois.nic.ipiranga",
            "itau": "whois.nic.itau",
            "iwc": "whois.nic.iwc",
            "jetzt": "whois.nic.jetzt",
            "jlc": "whois.nic.jlc",
            "jmp": "whois.nic.jmp",
            "jnj": "whois.nic.jnj",
            "jot": "whois.nic.jot",
            "joy": "whois.nic.joy",
            "jpmorgan": "whois.nic.jpmorgan",
            "jprs": "whois.nic.jprs",
            "kinder": "whois.nic.kinder",
            "kindle": "whois.nic.kindle",
            "kpmg": "whois.nic.kpmg",
            "kpn": "whois.nic.kpn",
            "kred": "whois.nic.kred",
            "kw": "whois.nic.kw",
            "lanxess": "whois.nic.lanxess",
            "lc": "whois2.afilias-grs.net",
            "lifeinsurance": "whois.nic.lifeinsurance",
            "like": "whois.nic.like",
            "lilly": "whois.nic.lilly",
            "lincoln": "whois.nic.lincoln",
            "living": "whois.nic.living",
            "lk": "whois.nic.lk",
            "loft": "whois.nic.loft",
            "lupin": "whois.nic.lupin",
            "maif": "whois.nic.maif",
            "marshalls": "whois.nic.marshalls",
            "mattel": "whois.nic.mattel",
            "mcd": "whois.nic.mcd",
            "mcdonalds": "whois.nic.mcdonalds",
            "merckmsd": "whois.nic.merckmsd",
            "microsoft": "whois.nic.microsoft",
            "mint": "whois.nic.mint",
            "mlb": "whois.nic.mlb",
            "mobily": "whois.nic.mobily",
            "moi": "whois.nic.moi",
            "montblanc": "whois.nic.montblanc",
            "moto": "whois.nic.moto",
            "msd": "whois.nic.msd",
            "mtpc": "whois.nic.mtpc",
            "mutual": "whois.nic.mutual",
            "mutuelle": "whois.nic.mutuelle",
            "nagoya": "whois.nic.nagoya",
            "nba": "whois.nic.nba",
            "netflix": "whois.nic.netflix",
            "neustar": "whois.nic.neustar",
            "nfl": "whois.nic.nfl",
            "nhk": "whois.nic.nhk",
            "nike": "whois.nic.nike",
            "northwesternmutual": "whois.nic.northwesternmutual",
            "now": " whois.nic.now",
            "ntt": "whois.nic.ntt",
            "nyc": "whois.nic.nyc",
            "office": "whois.nic.office",
            "okinawa": "whois.nic.okinawa",
            "oldnavy": "whois.nic.oldnavy",
            "open": "whois.nic.open",
            "orientexpress": "whois.nic.orientexpress",
            "otsuka": "whois.nic.otsuka",
            "passagens": "whois.nic.passagens",
            "pay": "whois.nic.pay",
            "pfizer": "whois.nic.pfizer",
            "pharmacy": " whois.nic.pharmacy",
            "piaget": " whois.nic.piaget",
            "pictet": "whois.nic.pictet",
            "pin": "whois.nic.pin",
            "ping": "whois.nic.ping",
            "pramerica": "whois.nic.pramerica",
            "praxi": "whois.nic.praxi",
            "prime": "whois.nic.prime",
            "pru": "whois.nic.pru",
            "prudential": "whois.nic.prudential",
            "ps": "whois.pnina.ps",
            "qvc": "whois.nic.qvc",
            "read": "whois.nic.read",
            "realtor": "whois.nic.realtor",
            "ren": "whois.nic.ren",
            "rocher": "whois.nic.rocher",
            "room": "whois.nic.room",
            "rw": "whois.ricta.org.rw",
            "ryukyu": "whois.nic.ryukyu",
            "safe": "whois.nic.safe",
            "safety": "whois.nic.safety",
            "sakura": "whois.nic.sakura",
            "sapo": "whois.nic.sapo",
            "sas": "whois.nic.sas",
            "save": "whois.nic.save",
            "secure": "whois.nic.secure",
            "sener": "whois.nic.sener",
            "shaw": "whois.afilias-srs.net",
            "shop": "whois.nic.shop",
            "silk": "whois.nic.silk",
            "skype": "whois.nic.skype",
            "sl": "whois.nic.sl",
            "smile": "whois.nic.smile",
            "sohu": "whois.nic.sohu",
            "song": "whois.nic.song",
            "spot": "whois.nic.spot",
            "staples": "whois.nic.staples",
            "statefarm": "whois.nic.statefarm",
            "stream": "whois.nic.stream",
            "suzuki": "whois.nic.suzuki",
            "swiftcover": "whois.nic.swiftcover",
            "talk": "whois.nic.talk",
            "taobao": "whois.nic.taobao",
            "target": "whois.nic.target",
            "tjmaxx": "whois.nic.tjmaxx",
            "tjx": "whois.nic.tjx",
            "tkmaxx": "whois.nic.tkmaxx",
            "tmall": "whois.nic.tmall",
            "tokyo": "whois.nic.tokyo",
            "tube": "whois.nic.tube",
            "tunes": "whois.nic.tunes",
            "tushu": "whois.nic.tushu",
            "tvs": "whois.nic.tvs",
            "unicom": "whois.nic.unicom",
            "uno": "whois.nic.uno",
            "vivo": "whois.nic.vivo",
            "vuelos": "whois.nic.vuelos",
            "wanggou": "whois.nic.wanggou",
            "watches": "whois.nic.watches",
            "weather": "whois.nic.weather",
            "weatherchannel": "whois.nic.weatherchannel",
            "weir": "whois.nic.weir",
            "whois": "whois.nic.qpon",
            "windows": "whois.nic.windows",
            "winners": "whois.nic.winners",
            "wow": "whois.nic.wow",
            "xbox": "whois.nic.xbox",
            "xn--1ck2e1b": "whois.nic.xn--1ck2e1b",
            "xn--2scrj9c": "whois.inregistry.net",
            "xn--3hcrj9c": "whois.inregistry.net",
            "xn--45br5cyl": "whois.inregistry.net",
            "xn--45brj9c": "whois.inregistry.net",
            "xn--8y0a063a": "whois.nic.xn--8y0a063a",
            "xn--bck1b9a5dre4c": "whois.nic.xn--bck1b9a5dre4c",
            "xn--cck2b3b": "whois.nic.xn--cck2b3b",
            "xn--czr694b": "whois.nic.xn--czr694b",
            "xn--e1a4c": "whois.eu",
            "xn--eckvdtc9d": "whois.nic.xn--eckvdtc9d",
            "xn--fct429k": "whois.nic.xn--fct429k",
            "xn--fpcrj9c3d": "whois.inregistry.net",
            "xn--fzc2c9e2c": "whois.nic.lk",
            "xn--g2xx48c": "whois.nic.xn--g2xx48c",
            "xn--gckr3f0f": "whois.nic.xn--gckr3f0f",
            "xn--gecrj9c": "whois.inregistry.net",
            "xn--gk3at1e": "whois.nic.xn--gk3at1e",
            "xn--h2breg3eve": "whois.inregistry.net",
            "xn--h2brj9c": "whois.inregistry.net",
            "xn--h2brj9c8c": "whois.inregistry.net",
            "xn--imr513n": "whois.nic.xn--imr513n",
            "xn--jvr189m": "whois.nic.xn--jvr189m",
            "xn--kpu716f": "whois.nic.xn--kpu716f",
            "xn--mgba3a3ejt": "whois.nic.xn--mgba3a3ejt",
            "xn--mgbb9fbpob": "whois.nic.xn--mgbb9fbpob",
            "xn--mgbbh1a": "whois.inregistry.net",
            "xn--mgbbh1a71e": "whois.inregistry.net",
            "xn--mgbgu82a": "whois.inregistry.net",
            "xn--nyqy26a": "whois.nic.xn--nyqy26a",
            "xn--otu796d": "whois.nic.xn--otu796d",
            "xn--pbt977c": "whois.nic.xn--pbt977c",
            "xn--rhqv96g": "whois.nic.xn--rhqv96g",
            "xn--rovu88b": "whois.nic.xn--rovu88b",
            "xn--rvc1e0am3e": "whois.inregistry.net",
            "xn--s9brj9c": "whois.inregistry.net",
            "xn--ses554g": "whois.registry.knet.cn",
            "xn--wgbh1c": "whois.dotmasr.eg",
            "xn--xkc2al3hye2a": "whois.nic.lk",
            "xn--xkc2dl3a5ee0h": "whois.inregistry.net",
            "yahoo": "whois.nic.yahoo",
            "yamaxun": "whois.nic.yamaxun",
            "yandex": "whois.nic.yandex",
            "yokohama": "whois.nic.yokohama",
            "you": "whois.nic.you",
            "za": "whois.registry.net.za",
            "zappos": "whois.nic.zappos",
            "zero": "whois.nic.zero",
            "zippo": "whois.nic.zippo",
        }

    def load(self):
        """
        Initiate the IANA database if it is not the case.
        """

        if "iana_db" not in PyFunceble.INTERN or not PyFunceble.INTERN["iana_db"]:
            # The global database is empty, None or does not exist.

            # We update it with the database content.
            PyFunceble.INTERN["iana_db"] = self.iana_db

    def _referer(self, extension):
        """
        Return the referer for the given extension.

        :param extension: A valid domain extension.
        :type extension: str

        :return: The whois server to use to get the WHOIS record.
        :rtype: str
        """

        # We get the a copy of the page.
        iana_record = self.lookup.whois(
            PyFunceble.CONFIGURATION["iana_whois_server"], "hello.%s" % extension
        )

        if iana_record and "refer" in iana_record:
            # The record is not empty.

            # We initiate a regex which will extract the referer.
            regex_referer = r"(?s)refer\:\s+([a-zA-Z0-9._-]+)\n"

            # We try to extract the referer.
            matched = Regex(
                iana_record, regex_referer, return_data=True, group=1
            ).match()

            if matched:
                # The referer was extracted successfully.

                # We return the matched referer.
                return matched

        # * The referer was not extracted successfully.
        # or
        # * The iana record is empty.

        if extension in self.manual_server:
            # The extension is in the list of manual entries.

            # We return the server which we set manually.
            return self.manual_server[extension]

        # We return None because we weren't able to get the server to call for
        # the given extension.
        return None

    def _extensions(self):
        """
        Extract the extention from the given block.
        Plus get its referer.
        """

        upstream_lines = (
            Download(self.iana_url, return_data=True)
            .text()
            .split('<span class="domain tld">')
        )

        # We extract the different extension from the currently readed line.
        regex_valid_extension = r"(/domains/root/db/)(.*)(\.html)"

        for block in upstream_lines:
            if "/domains/root/db/" in block:
                # The link is in the line.

                # We try to extract the extension.
                matched = Regex(
                    block, regex_valid_extension, return_data=True, rematch=True
                ).match()[1]

                if matched:
                    # The extraction is not empty or None.

                    # We get the referer.
                    referer = self._referer(matched)

                    # We yield the matched extension and its referer.
                    yield (matched, referer)

    def update(self):
        """
        Update the content of the `iana-domains-db` file.
        """

        if not PyFunceble.CONFIGURATION["quiet"]:
            # * The quiet mode is not activated.

            # We print on screen what we are doing.
            print("Update of iana-domains-db", end=" ")

        # We loop through the line of the iana website.
        for extension, referer in self._extensions():

            if extension not in self.iana_db or self.iana_db[extension] != referer:
                # We add the extension to the databae.
                self.iana_db[extension] = referer

                # We save the content of the constructed database.
                Dict(self.iana_db).to_json(self.destination)

        if not PyFunceble.CONFIGURATION["quiet"]:
            # The quiet mode is not activated.

            # We indicate that the work is done without any issue.
            print(PyFunceble.INTERN["done"])