#!/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 give us the mining interface and 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 urllib3.exceptions as urllib3_exceptions

import PyFunceble
from PyFunceble.check import Check
from PyFunceble.helpers import Dict, File, List

class Mining:
    """
    Manage the minig subsystem.
    """

    def __init__(self):  # pragma: no cover
        if "to_test" in PyFunceble.INTERN and PyFunceble.INTERN["to_test"]:
            # There is something to test.

            if PyFunceble.INTERN["to_test_type"] == "domain":
                # We are testing a domain.

                # We set a variable which will save the actual element we are working with.
                self.to_get = "http://%s:80" % PyFunceble.INTERN["to_test"]
                self.to_get_bare = PyFunceble.INTERN["to_test"]
            elif PyFunceble.INTERN["to_test_type"] == "url":
                # We are testing an URL.

                # We set a variable which will save the actual element we are working with.
                self.to_get = PyFunceble.INTERN["to_test"]
                self.to_get_bare = PyFunceble.INTERN["to_test"]
            else:
                raise Exception("Unknow test type.")

        if PyFunceble.CONFIGURATION["user_agent"]:
            # The user-agent is given.

            # We append the user agent to the header we are going to parse with
            # the request.
            self.headers = {"User-Agent": PyFunceble.CONFIGURATION["user_agent"]}
        else:
            # The user-agent is not given or is empty.

            # We return an empty header.
            self.headers = {}

        # We get the file we are going to save our data.
        self.file = (
            PyFunceble.CURRENT_DIRECTORY + PyFunceble.OUTPUTS["default_files"]["mining"]
        )

        if "mined" not in PyFunceble.INTERN:
            # The mined index is not into the configuration informations.

            # We initiate it.
            self._retrieve()

            # We backup everything.
            self._backup()

    def mine(self):  # pragma: no cover
        """
        Search for domain or URL related to the original URL or domain.

        :return: The mined domains or URL.
        :rtype: dict
        """

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            try:
                # We get the history.
                history = PyFunceble.requests.get(
                    self.to_get,
                    timeout=PyFunceble.CONFIGURATION["seconds_before_http_timeout"],
                    headers=self.headers,
                ).history

                # We initiate a dictionnary which will save the
                # list of mined links.
                mined = {self.to_get_bare: []}

                for element in history:
                    # We loop through the history.

                    # We update the element.
                    element = element.url

                    if PyFunceble.INTERN["to_test_type"] == "url":
                        # We are testing a full url.

                        # We get the element to append.
                        to_append = Check().is_url_valid(element, return_base=False)
                    elif PyFunceble.INTERN["to_test_type"] == "domain":
                        # We are testing a domain.

                        # We get the element to append.
                        to_append = Check().is_url_valid(element, return_base=True)
                    else:
                        raise Exception("Unknown tested.")

                    if to_append:
                        # There is something to append.

                        if to_append.endswith(":80"):
                            # The port is present.

                            # We get rid of it.
                            to_append = to_append[:-3]

                        if to_append != self.to_get_bare:
                            # The element to append is different as
                            # the element we are globally testing.

                            # We append the element to append to the
                            # list of mined links.
                            mined[self.to_get_bare].append(to_append)

                if mined[self.to_get_bare]:
                    # There is something in the list of mined links.

                    # We return the whole element.
                    return mined

                # There is nothing in the list of mined links.

                # We return None.
                return None

            except (
                PyFunceble.requests.ConnectionError,
                PyFunceble.requests.exceptions.Timeout,
                PyFunceble.requests.exceptions.InvalidURL,
                PyFunceble.socket.timeout,
                urllib3_exceptions.InvalidHeader,
                UnicodeDecodeError,  # The probability that this happend in production is minimal.
            ):
                # Something went wrong.

                # We return None.
                return None
        return None

    def _retrieve(self):
        """
        Retrieve the mining informations.
        """

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            if "mined" not in PyFunceble.INTERN:
                PyFunceble.INTERN["mined"] = {}

            if PyFunceble.path.isfile(self.file):
                # Our backup file exist.

                # We return the information from our backup.
                data = Dict().from_json(File(self.file).read())

                # We clean the empty elements.
                for file_path in data:
                    PyFunceble.INTERN["mined"][file_path] = {}

                    for element in data[file_path]:
                        if data[file_path][element]:
                            PyFunceble.INTERN["mined"][file_path][element] = data[
                                file_path
                            ][element]

                return
        # * The mining is not activated.
        # or
        # * Our backup file does not exist.

        # We return nothing.
        PyFunceble.INTERN["mined"] = {}

        return

    def _backup(self):
        """
        Backup the mined informations.
        """

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            # We backup our mined informations.
            Dict(PyFunceble.INTERN["mined"]).to_json(self.file)

    def _add(self, to_add):
        """
        Add the currently mined information to the
        mined "database".

        :param to_add: The element to add.
        :type to_add: dict
        """

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            if PyFunceble.INTERN["file_to_test"] not in PyFunceble.INTERN["mined"]:
                # Our tested file path is not into our mined database.

                # We initiate it.
                PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]] = {}

            for element in to_add:
                # We loop through the element to add.

                if (
                    element
                    in PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]]
                ):
                    # The element is already into the tested file path database.

                    # We extent it with our element to add.
                    PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]][
                        element
                    ].extend(to_add[element])
                else:
                    # The element is already into the tested file path database.

                    # We initiate it.
                    PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]][
                        element
                    ] = to_add[element]

                # We format the added information in order to avoid duplicate.
                PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]][
                    element
                ] = List(
                    PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]][
                        element
                    ]
                ).format()

            # We backup everything.
            self._backup()

    def remove(self):
        """
        Remove the currently tested element from the mining
        data.
        """

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            if PyFunceble.INTERN["file_to_test"] in PyFunceble.INTERN["mined"]:
                # The currently tested file is in our mined database.

                for element in PyFunceble.INTERN["mined"][
                    PyFunceble.INTERN["file_to_test"]
                ]:
                    # We loop through the mined index.

                    if (
                        self.to_get_bare
                        in PyFunceble.INTERN["mined"][
                            PyFunceble.INTERN["file_to_test"]
                        ][element]
                    ):
                        # The currently read element content.

                        # We remove the globally tested element from the currently
                        # read element content.
                        PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]][
                            element
                        ].remove(self.to_get_bare)

                # We backup everything.
                self._backup()

    @classmethod
    def list_of_mined(cls):
        """
        Provide the list of mined so they can be added to the list
        queue.

        :return: The list of mined domains or URL.
        :rtype: list
        """

        # We initiate a variable which will return the result.
        result = []

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            if PyFunceble.INTERN["file_to_test"] in PyFunceble.INTERN["mined"]:
                # The file we are testing is into our mining database.

                for element in PyFunceble.INTERN["mined"][
                    PyFunceble.INTERN["file_to_test"]
                ]:
                    # We loop through the list of index of the file we are testing.

                    # We append the element of the currently read index to our result.
                    result.extend(
                        PyFunceble.INTERN["mined"][PyFunceble.INTERN["file_to_test"]][
                            element
                        ]
                    )

                # We format our result.
                result = List(result).format()

        # We return the result.
        return result

    def process(self):  # pragma: no cover
        """
        Process the logic and structuration of the mining database.
        """

        if PyFunceble.CONFIGURATION["mining"]:
            # The mining is activated.

            # We load the mining logic.
            mined = self.mine()

            if mined:
                # The mined data is not empty or None.

                # We add the mined data to the global database.
                self._add(mined)

                # And we finally backup everything.
                self._backup()


#!/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 logs 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, File

class Logs:  # pragma: no cover
    """
    Provide a clean and unique way to work with logs.
    Indeed, it's not good to have logs spread around the code :smile:

    :param output: A path to the JSON file we are going to write.
    :type output: str
    """

    def __init__(self, output=None):
        self.output = output
        self.current_time = str(PyFunceble.time())

    @classmethod
    def _get_content(cls, file):
        """
        Get and return the content of the given log file.

        :param file: The file we have to get the content from.
        :type file: str

        :return The content of the given file.
        :rtype: dict
        """

        if PyFunceble.path.isfile(file):
            return Dict().from_json(File(file).read())

        return {}

    @classmethod
    def _write_content(cls, content, file):
        """
        Write the content into the given file.

        :param content: The dict to write.
        :type content: dict

        :param file: The file to write.
        :type file: str
        """

        if not PyFunceble.CONFIGURATION["no_files"]:
            if not isinstance(content, dict):
                content = {}

            Dict(content).to_json(file)

    def whois(self, record):
        """
        Logs the WHOIS record if needed.

        :param record: The record to log.
        :type record: str
        """

        if PyFunceble.CONFIGURATION["debug"] and PyFunceble.CONFIGURATION["logs"]:
            # The debug and the logs subsystem are activated.

            if PyFunceble.INTERN["referer"]:
                referer = PyFunceble.INTERN["referer"]
            else:
                referer = None

            to_write = {
                self.current_time: {
                    "domain": PyFunceble.INTERN["to_test"],
                    "record": record,
                    "referer": referer,
                }
            }

            if self.output:
                output = self.output
            else:
                output = PyFunceble.OUTPUT_DIRECTORY
                output += PyFunceble.OUTPUTS["parent_directory"]
                output += PyFunceble.OUTPUTS["logs"]["directories"]["parent"]
                output += PyFunceble.OUTPUTS["logs"]["filenames"]["whois"]

            current_content = self._get_content(output)
            current_content.update(to_write)

            self._write_content(current_content, output)

    def expiration_date(self, extracted):
        """
        Logs the extracted expiration date.

        :param extracted: The extracted expiration date (from WHOIS record).
        :type extracted: str
        """

        if PyFunceble.CONFIGURATION["logs"]:
            # The logs subsystem is activated.

            if PyFunceble.INTERN["referer"]:
                referer = PyFunceble.INTERN["referer"]
            else:
                referer = None

            to_write = {
                self.current_time: {
                    "domain": PyFunceble.INTERN["to_test"],
                    "expiration_date": extracted,
                    "whois_server": referer,
                }
            }

            if self.output:
                output = self.output
            else:
                output = PyFunceble.OUTPUT_DIRECTORY
                output += PyFunceble.OUTPUTS["parent_directory"]
                output += PyFunceble.OUTPUTS["logs"]["directories"]["parent"]
                output += PyFunceble.OUTPUTS["logs"]["filenames"]["date_format"]

            current_content = self._get_content(output)
            current_content.update(to_write)

            self._write_content(current_content, output)

            if PyFunceble.CONFIGURATION["share_logs"]:
                # The logs sharing is activated.

                # And we share the logs with the api.
                PyFunceble.requests.post(
                    PyFunceble.LINKS["api_date_format"],
                    data=to_write[self.current_time],
                )

    def referer_not_found(self, extension):
        """
        Logs the case that the referer was not found.

        :param extension: The extension of the domain we are testing.
        :type extension: str
        """

        if PyFunceble.CONFIGURATION["logs"]:
            # The logs subsystem is activated.

            to_write = {
                self.current_time: {
                    "domain": PyFunceble.INTERN["to_test"],
                    "extension": extension,
                }
            }

            if self.output:
                output = self.output
            else:
                output = PyFunceble.OUTPUT_DIRECTORY
                output += PyFunceble.OUTPUTS["parent_directory"]
                output += PyFunceble.OUTPUTS["logs"]["directories"]["parent"]
                output += PyFunceble.OUTPUTS["logs"]["filenames"]["no_referer"]

            current_content = self._get_content(output)
            current_content.update(to_write)

            self._write_content(current_content, output)

            if PyFunceble.CONFIGURATION["share_logs"]:
                # The logs sharing is activated.

                # And we share the logs with the api.
                PyFunceble.requests.post(
                    PyFunceble.LINKS["api_no_referer"], data=to_write[self.current_time]
                )


#!/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 printing interface and 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, File, List
from PyFunceble.sort import Sort

class Prints:
    """
    Print data on screen and into a file if needed.
    Template Possibilities: Percentage, Less, HTTP and any status you want.

    :param to_print: The list of data to print.
    :type to_pritn: list

    :param template:
        The template to use.

        .. note::
            Available templates:

                - :code:`Percentage`
                - :code:`Less`
                - :code:`HTTP`
                - any of the official status.

    :type template: str

    :param output_file: The path to the file to write.
    :type output_file: str

    :param only_on_file:
        Tell us if we only have to print on file and not on screen.
    :type only_on_file: bool
    """

    def __init__(self, to_print, template, output_file=None, only_on_file=False):
        # We get the template.
        self.template = template
        # We get the output.
        self.output = output_file
        # We get the data to print.
        self.data_to_print = to_print
        # We get the state of the only on file.
        self.only_on_file = only_on_file

        # We initiate the variable which will save the list of header.
        # Note: We initiate an Ordered Dict because we want to keep
        # the order.
        self.headers = PyFunceble.OrderedDict()

        # We iniate the Generic header and the spacement of each colomns.
        self.headers["Generic"] = PyFunceble.OrderedDict(
            zip(
                [
                    "Domain",
                    "Status",
                    "Expiration Date",
                    "Source",
                    "HTTP Code",
                    "Analyze Date",
                ],
                [100, 11, 17, 10, 10, 20],
            )
        )

        # We iniate the official UP header and the spacement of each colomns.
        self.headers[PyFunceble.STATUS["official"]["up"]] = PyFunceble.OrderedDict(
            zip(
                ["Domain", "Expiration Date", "Source", "HTTP Code", "Analyze Date"],
                [100, 17, 10, 10, 20],
            )
        )

        # We iniate the official VALID header and the spacement of each colomns.
        self.headers[PyFunceble.STATUS["official"]["valid"]] = PyFunceble.OrderedDict(
            zip(["Domain", "Source", "Analyze Date"], [100, 10, 20])
        )

        # We iniate the official DOWN header and the spacement of each colomns.
        self.headers[PyFunceble.STATUS["official"]["down"]] = PyFunceble.OrderedDict(
            zip(
                [
                    "Domain",
                    "WHOIS Server",
                    "Status",
                    "Source",
                    "HTTP Code",
                    "Analyze Date",
                ],
                [100, 35, 11, 10, 10, 20],
            )
        )

        # We iniate the official INVALID header and the spacement of each colomns.
        self.headers[PyFunceble.STATUS["official"]["invalid"]] = PyFunceble.OrderedDict(
            zip(["Domain", "Source", "HTTP Code", "Analyze Date"], [100, 10, 10, 20])
        )

        # We iniate the official LESS header and the spacement of each colomns.
        self.headers["Less"] = PyFunceble.OrderedDict(
            zip(["Domain", "Status", "HTTP Code"], [100, 11, 10])
        )

        # We iniate the official Percentage header and the spacement of each colomns.
        self.headers["Percentage"] = PyFunceble.OrderedDict(
            zip(["Status", "Percentage", "Numbers"], [11, 12, 12])
        )

        # We iniate the official HTTP header and the spacement of each colomns.
        self.headers["HTTP"] = PyFunceble.OrderedDict(
            zip(["Domain", "Status", "HTTP Code", "Analyze Date"], [100, 11, 10, 20])
        )

        # We initiate a variable which will save the currently in use header.
        self.currently_used_header = {}

    def _before_header(self):
        """
        Print informations about PyFunceble and the date of generation of a file
        into a given path, if doesn't exist.
        """

        if (
            not PyFunceble.CONFIGURATION["no_files"]
            and self.output
            and not PyFunceble.path.isfile(self.output)
        ):
            # * We are allowed to generate files.
            # and
            # * And output is given.
            # and
            # * The given output does not exist.

            # We initiate the information about what generated the file.
            link = "# File generated by %s\n" % PyFunceble.LINKS["repo"]

            # We initiate the information about the generation date of this file.
            date_of_generation = (
                "# Date of generation: %s \n\n" % PyFunceble.CURRENT_TIME
            )

            # We initiate a variable which will save the list of
            # templates which have to meet in order to write the before
            # header informations.
            authorized_templates = [
                "Generic_File",
                PyFunceble.STATUS["official"]["up"],
                PyFunceble.STATUS["official"]["down"],
                PyFunceble.STATUS["official"]["invalid"],
                PyFunceble.STATUS["official"]["valid"],
                "Less",
            ]

            if self.template in authorized_templates:
                # The current header is in our list of authorized templated.

                # We get the header.
                header = (
                    self._header_constructor(self.currently_used_header, None)[0] + "\n"
                )

            try:
                # We try to print the link, the date of generation and the header in the
                # given file.
                File(self.output).write(link + date_of_generation + header)
            except UnboundLocalError:
                # We don't have any header.

                # We print the link and the date in the given file.
                File(self.output).write(link + date_of_generation)

    @classmethod
    def _header_constructor(
        cls, data_to_print, header_separator="-", column_separator=" "
    ):
        """
        Construct header of the table according to template.

        :param data_to_print:
            The list of data to print into the header of the table.
        :type data_to_print: list

        :param header_separator:
            The separator to use between the table header and our data.
        :type header_separator: str

        :param colomn_separator: The separator to use between each colomns.
        :type colomn_separator: str

        :return: The data to print in a list format.
        :rtype: list
        """

        # We initiate a variable which will save the header data.
        header_data = []

        # We initiate a variable which will save the header sizes.
        header_size = ""

        # We initiate the glue to set before the size.
        before_size = "%-"

        # We initiate the glue to set after the size.
        after_size = "s"

        if header_separator:
            # The header separator is not empty.

            # We initiate a variable which will save the list of
            # separator data.
            header_separator_data = []

        # We get the length of the data to print.
        length_data_to_print = len(data_to_print) - 1

        # We initiate an iterator.
        i = 0

        for data in data_to_print:
            # We loop through the list of data.

            # We get the size of the currently read data.
            size = data_to_print[data]

            # We append the data to the header data list.
            header_data.append(data)

            # We construct the header size.
            # Note: our header size is formatted line %s-sizes
            # (the s at the end is part of the formatting.)
            header_size += before_size + str(size) + after_size

            if i < length_data_to_print:
                # The iterator is less than the length of data to print.

                # We append the the colomn separator to the header size.
                header_size += column_separator

            if header_separator:
                # The header separator is given.

                # We append the right size of separator to the list of
                # separator data.
                header_separator_data.append(header_separator * size)

            # We increase the iterator.
            i += 1

        if header_separator:
            # The header separator is given.

            return [
                # We return the formatted header (like we will do with print('%s' % 'hello'))
                header_size % tuple(header_data),
                # We return the formatted header separator.
                header_size % tuple(header_separator_data),
            ]

        # The header separator is not given.

        # We return the formetted header.
        return [header_size % tuple(header_data)]

    def header(
        self, do_not_print=False
    ):  # pragma: no cover pylint: disable=too-many-branches
        """
        Management and creation of templates of header.
        Please consider as "header" the title of each columns.

        :param do_not_print:
            Tell us if we have to print the header or not.
        :type do_not_print: bool
        """

        if (
            not PyFunceble.CONFIGURATION["header_printed"]
            or self.template == "Percentage"
            or do_not_print
        ):
            # * The header has not been already printed.
            # or
            # * The template is the `Percentage template`.
            # or
            # * We are authorized to print something.

            if (
                self.template.lower() in PyFunceble.STATUS["list"]["generic"]
                or self.template == "Generic_File"
            ):
                # * The template is into the list of generic status.
                # or
                # * The template is equal to `Generic_File`.

                # The data to print is the Generic header.
                to_print = self.headers["Generic"]

                if (
                    self.template.lower() in PyFunceble.STATUS["list"]["generic"]
                    and PyFunceble.HTTP_CODE["active"]
                ):
                    # * The template is in the list of generic status.
                    # and
                    # * the http status code extraction is activated.

                    # We remove the Analyze Date colomn from the data to print.
                    to_print = Dict(to_print).remove_key("Analyze Date")
            elif self.template.lower() in PyFunceble.STATUS["list"]["up"]:
                # The template is in the list of up status.

                # We informations to print is the up header.
                to_print = self.headers[PyFunceble.STATUS["official"]["up"]]
            elif self.template.lower() in PyFunceble.STATUS["list"]["valid"]:
                # The template is in the list of valid status.

                # We informations to print is the valid header.
                to_print = self.headers[PyFunceble.STATUS["official"]["valid"]]
            elif self.template.lower() in PyFunceble.STATUS["list"]["down"]:
                # The template is in the list of down status.

                # We informations to print is the down header.
                to_print = self.headers[PyFunceble.STATUS["official"]["down"]]
            elif self.template.lower() in PyFunceble.STATUS["list"]["invalid"]:
                # The template is in the list of invalid status.

                # We informations to print is the invalid header.
                to_print = self.headers[PyFunceble.STATUS["official"]["invalid"]]
            elif (
                self.template == "Less"
                or self.template == "Percentage"
                or self.template == "HTTP"
            ):  # pylint: disable=line-too-long
                # * The template is equal to `Less`.
                # or
                # * The template is equal to `Percentage`.
                # or
                # * The template is equal to `HTTP`.

                # We get the header with the help of the template name.
                to_print = self.headers[self.template]

                if self.template == "Less" and not PyFunceble.HTTP_CODE["active"]:
                    # * The template is equal to `Less`.
                    # and
                    # * The http status code extraction is deactivated.

                    # We append the source index to the header.
                    to_print["Source"] = 10

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

                # We remove the HTTP Code index from the data to print.
                to_print = Dict(to_print).remove_key("HTTP Code")

            # We update the currently used header.
            self.currently_used_header = to_print

            if not do_not_print:
                # We are not authorized to print anything.

                # We generate the before header.
                self._before_header()

                for formatted_template in self._header_constructor(to_print):
                    # We loop through the formatted template.

                    if not self.only_on_file:
                        # We do not have to print only on file.

                        # We print on screen the formatted header template.
                        print(formatted_template)

                    if not PyFunceble.CONFIGURATION["no_files"] and self.output:
                        # An output destination is given.

                        # We write the file with the formatted header template.
                        File(self.output).write(formatted_template + "\n")

    def _data_constructor(self, size):
        """
        Construct the table of data according to given size.

        :param size: The maximal length of each string in the table.
        :type size: list

        :return:
            A dict with all information about the data and how to which what
            maximal size to print it.
        :rtype: OrderedDict

        :raises:
            :code:`Exception`
                If the data and the size does not have the same length.
        """

        # We initiate a variable which will save what we are going to
        # return.
        result = PyFunceble.OrderedDict()

        if len(self.data_to_print) == len(size):
            # The length of the data to print is equal to the length of the given size.

            for i in range(len(self.data_to_print)):
                # We loop until our iterator is less or equal to the length of the data
                # to print.

                # We initiate the result index and its size.
                result[self.data_to_print[i]] = size[i]
        else:
            # This should never happend. If it's happens then there is something
            # wrong from the inputed data.
            raise Exception(
                "Inputed: " + str(len(self.data_to_print)) + "; Size: " + str(len(size))
            )

        # We return the constructed result.
        return result

    @classmethod
    def _size_from_header(cls, header):
        """
        Get the size of each columns from the header.

        :param header:
            The header template we have to get the size from.
        :type header: dict

        :return: The maximal size of the each data to print.
        :rtype: list
        """

        # We initiate the result we are going to return.
        result = []

        for data in header:
            # We lopp through the header.

            # And we append the size to our result.
            result.append(header[data])

        # We return the result.
        return result

    def _colorify(self, data):
        """
        Retun colored string.

        :param data: The string to colorify.
        :type data: str

        :return: A colored string.
        :rtype: str
        """

        if self.template in ["Generic", "Less"]:
            # The template is in the list of template that need the coloration.

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

                # We print the data with a green background.
                data = PyFunceble.Fore.BLACK + PyFunceble.Back.GREEN + data
            elif self.data_to_print[1].lower() in PyFunceble.STATUS["list"]["down"]:
                # The status is in the list of down status.

                # We print the data with a red background.
                data = PyFunceble.Fore.BLACK + PyFunceble.Back.RED + data
            else:
                # The status is not in the list of up and down status.

                # We print the data with a cyan background.
                data = PyFunceble.Fore.BLACK + PyFunceble.Back.CYAN + data

        # We return the data.
        return data

    def _json_print(self):  # pragma: no cover
        """
        Management of the json template.
        """

        if self.output:
            # The given output is not empty.

            if PyFunceble.path.isfile(self.output):
                # The given output already exist.

                # We get the content of the output.
                content = Dict().from_json(File(self.output).read())

                if isinstance(content, list):
                    # The content is a list.

                    # We extend the content with our data to print.
                    content.extend(self.data_to_print)

                    # We format our list.
                    content = List(content).custom_format(Sort.standard)

                    if PyFunceble.CONFIGURATION["hierarchical_sorting"]:
                        # The hierarchical sorting is activated.

                        # We format our content hierarchicaly
                        content = List(content).custom_format(Sort.hierarchical)

                    # We finally save our content into the file.
                    Dict(content).to_json(self.output)
                else:
                    # The content is not a list.

                    # We raise an exception.
                    raise Exception("Output not correctly formatted.")
            else:
                # The given output does not already exist.

                # We save our data to print into the output.
                #
                # Note: We do not have to take care if self.data_to_print is a list
                # formatted or not because this method should not be called if it is
                # not the case.
                Dict(self.data_to_print).to_json(self.output)
        else:
            # The given output is empty.

            # We raise an exception.
            raise Exception("Empty output given.")

    def data(self):  #  pragma: no cover  pylint: disable=inconsistent-return-statements
        """
        Management and input of data to the table.

        :raises:
            :code:`Exception`
                When self.data_to_print is not a list.
        """

        if isinstance(self.data_to_print, list):
            # The data to print is a list.

            # We initiate the data we are going to print.
            to_print = {}

            # We initiate the size we are going to print.
            to_print_size = []

            # We initiate a variable which will list the list of
            # alone case.
            alone_cases = ["Percentage", "HTTP"]

            # we initiate a variable which will list the list of
            # template which does not need a header.
            without_header = ["FullHosts", "PlainDomain"]

            if self.template.lower() == "json":
                # The template is the json template.

                if not PyFunceble.CONFIGURATION["no_files"] and self.output:
                    # * We are allowed to generate file.
                    # and
                    # * The given output is not empty.

                    # We print the json file.
                    return self._json_print()

                # We return nothing.
                return None

            if self.template not in alone_cases and self.template not in without_header:
                # * The template is not in the list of alone case.
                # and
                # * THe template is not in the list of template without header.

                # We get the template we should use.
                # Note: We basically only need the self.currently_used_header to be filled.
                self.header(True)

                # And we get the size from the header.
                to_print_size = self._size_from_header(self.currently_used_header)
            elif self.template in without_header:
                # The template is in the list of template which does not need a header.

                for data in self.data_to_print:
                    # We loop through the list of data to print.

                    # And we construct the (spacement) size of the data to print.
                    to_print_size.append(str(len(data)))
            else:
                # We get the size from the given template name.
                to_print_size = self._size_from_header(self.headers[self.template])

            # We construct and format the data to print.
            to_print = self._data_constructor(to_print_size)

            # We print the before header section.
            self._before_header()

            for data in self._header_constructor(to_print, False):
                # We loop through the formatted data.

                if self.template.lower() in PyFunceble.STATUS["list"][
                    "generic"
                ] or self.template in ["Less", "Percentage"]:
                    # * The template is in the list of generic status.
                    # or
                    # * The template is in a specific list.

                    if not self.only_on_file:
                        # We are authorized to print on screen.

                        # We colorify the data to print.
                        colorified_data = self._colorify(data)

                        # And we print the data.
                        print(colorified_data)
                if not PyFunceble.CONFIGURATION["no_files"] and self.output:
                    # * We are authorized to print on any file.
                    # and
                    # * The output is given.

                    # We write our data into the printed file.
                    File(self.output).write(data + "\n")
        else:
            # This should never happend. If it's happens then there's a big issue
            # around data_to_print.
            raise Exception("Please review Prints().data()")


#!/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 execution time 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.directory_structure import DirectoryStructure
from PyFunceble.helpers import Dict, File

class ExecutionTime:  # pylint: disable=too-few-public-methods
    """
    Set and return the exection time of the program.

    :param action:
        The action related the execution time.
        Can be `start` or `stop`.
    :type action: str

    :param last:
        Tell the subsystem if we are at the very end of the file testing.
    :type last: bool
    """

    def __init__(self, action="start", last=False):
        # We parse the action to the class scope.
        self.action = action

        if self._authorization():
            # * The execution time disaply is activated.
            # or
            # * The Travis CI mode is activated.

            if self.action == "start":
                # The action is equal to `start`.

                # We set the starting time.
                self._starting_time()
            elif self.action == "stop":
                # * The action is not equal to `start`.
                # and
                # * The action is equal to `stop`

                # We set the ending time.
                self._stoping_time()

                # And we print the execution time.
                print(
                    PyFunceble.Fore.MAGENTA
                    + PyFunceble.Style.BRIGHT
                    + "\nExecution time: "
                    + self.format_execution_time()
                )

        self._save(last=last)

    @classmethod
    def _authorization(cls):  # pragma: no cover
        """
        Check the execution authorization.

        :return: The authorization status.
        :rtype: bool
        """

        if (
            PyFunceble.CONFIGURATION["show_execution_time"]
            or PyFunceble.CONFIGURATION["travis"]
        ):
            return True

        return False

    def _save(self, last=False):  # pragma: no cover
        """
        Save the current time to the file.

        :param last:
            Tell us if we are at the very end of the file testing.
        :type last: bool
        """

        if (
            self._authorization()
            and PyFunceble.CONFIGURATION["logs"]
            and "file_to_test" in PyFunceble.INTERN
            and PyFunceble.INTERN["file_to_test"]
        ):
            # * We are authorized to work.
            # and
            # * The generation of logs is activated.
            # and
            # * We are not testing as an imported module.

            # We set the location of the file we are working with.
            self.file = (
                PyFunceble.OUTPUT_DIRECTORY
                + PyFunceble.OUTPUTS["parent_directory"]
                + PyFunceble.OUTPUTS["logs"]["directories"]["parent"]
                + PyFunceble.OUTPUTS["logs"]["filenames"]["execution_time"]
            )

            if PyFunceble.path.isfile(self.file):
                # The file we are working with exist.

                # We get its content so we can directly work with it.
                content = Dict().from_json(File(self.file).read())
            else:
                # The file we are working with does not exist.

                # We generate a dummy content.
                content = {}

            if self.action == "start":
                # The action is equal to `start`.

                if "final_total" in content and content["final_total"]:
                    # The final total index exist.

                    # We delete it.
                    del content["final_total"]

                if "data" in content:
                    # The data index exist.

                    # We append the current start time inside it at
                    # a new sublist.
                    content["data"].append([PyFunceble.INTERN["start"]])
                else:
                    # The data index does not exist.

                    # We create the index along with the current start time.
                    content["data"] = [[PyFunceble.INTERN["start"]]]
            elif self.action == "stop":
                # The action is equal to `stop`.

                try:
                    # We try to work with the data index.

                    # We append the end time at the end of the last element
                    # of data.
                    #
                    # Note: It is at the end because we should have as first
                    # the star time.
                    content["data"][-1].append(PyFunceble.INTERN["end"])

                    # We get the start time.
                    start = content["data"][0][0]
                    # We get the end time.
                    end = content["data"][-1][-1]

                    # We calculate the execution time of the test.
                    content["current_total"] = self.format_execution_time(start, end)

                    if last:
                        # We are at the very end of the file testing.

                        # We initiate the global execution time.
                        content["final_total"] = content["current_total"]

                        # We inform the user about the global execution time.
                        print(
                            PyFunceble.Fore.MAGENTA
                            + PyFunceble.Style.BRIGHT
                            + "Global execution time: "
                            + content["final_total"]
                        )
                except KeyError:
                    # It is not possible to work with the data index because
                    # it does not exist.

                    # We ignore the problem.
                    pass

            try:
                # We try to save the whole data at its final location.
                Dict(content).to_json(self.file)
            except FileNotFoundError:
                # The directory was not found.

                # We construct the output directory
                DirectoryStructure()

                # And we retry to save the whole data at its final location.
                Dict(content).to_json(self.file)

    @classmethod
    def _starting_time(cls):  # pragma: no cover
        """
        Set the starting time.
        """

        # We set the starting time as the current time.
        PyFunceble.INTERN["start"] = int(PyFunceble.time())

    @classmethod
    def _stoping_time(cls):  # pragma: no cover
        """
        Set the ending time.
        """

        # We set the ending time as the current time.
        PyFunceble.INTERN["end"] = int(PyFunceble.time())

    @classmethod
    def _calculate(cls, start=None, end=None):
        """
        calculate the difference between starting and ending time.

        :param start: A starting time.
        :type start: int|str

        :param stop: A ending time.
        :type stop: int|str

        :return:
            A dict with following as index.

                * :code:`days`
                * :code:`hours`
                * :code:`minutes`
                * :code:`seconds`

            as index.
        :rtype: dict
        """

        if start and end:
            # The start and end time is explicitly given.

            # We get the difference between the ending and the starting time.
            time_difference = int(end) - int(start)
        else:
            # The start and end time is not explicitly given.

            # We get the difference between the ending and the starting time.
            time_difference = PyFunceble.INTERN["end"] - PyFunceble.INTERN["start"]

        # We initiate an OrderedDict.
        # Indeed, we use an ordered dict because we want the structuration and the
        # order to stay always the same.
        # As a dictionnary is always unordered, we can use it. Otherwise the time will
        # not be shown correctly.
        data = PyFunceble.OrderedDict()

        # We calculate and append the day to our data.
        data["days"] = str(time_difference // (24 * 60 * 60)).zfill(2)

        # We calculate and append the hours to our data.
        data["hours"] = str((time_difference // (60 * 60)) % 24).zfill(2)

        # We calculate and append the minutes to our data.
        data["minutes"] = str((time_difference % 3600) // 60).zfill(2)

        # We calculate and append the minutes to our data.
        data["seconds"] = str(time_difference % 60).zfill(2)

        # We finaly return our data.
        return data

    def format_execution_time(self, start=None, end=None):
        """
        Format the calculated time into a human readable format.

        :param start: A starting time.
        :type start: int|str

        :param stop: A ending time.
        :type stop: int|str

        :return: A human readable date.
        :rtype: str
        """

        # We return the formatted execution time.
        return ":".join(list(self._calculate(start, end).values()))


#!/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 give us the cleaning 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.config import Load
from PyFunceble.core import Core
from PyFunceble.helpers import File

class Clean:
    """
    Provide the cleaning logic(s).

    :param list_to_test: The list of domains we are testing.
    :type list_to_test: list|None

    :param clean_all:
        Tell the subsystem if we need to clean all.
        Which include, of course, the output directory but also
        all other file(s) generated by our system.
    :type clean_all: bool
    """

    def __init__(self, list_to_test, clean_all=False):
        if list_to_test:
            # The list to test is not empty.

            try:
                # We try to see if we have to reset counters and clean the output directory.

                # We get the number of tested.
                number_of_tested = PyFunceble.INTERN["counter"]["number"]["tested"]

                if (
                    number_of_tested == 0
                    or list_to_test[number_of_tested - 1] == list_to_test[-1]
                    or number_of_tested >= len(list_to_test)
                ):
                    # * If the number of tested is null,
                    # or
                    # * the last tested element is the same as the last element in the
                    #   sequence,
                    # or
                    # * The number of tested is equal to the number of elements in the
                    #   sequence,

                    # We reset the counters.
                    Core.reset_counters()

                    # We clean the output directory.
                    self.almost_everything(clean_all)
            except IndexError:
                # But if at any time in the conditionnal an Index Error occurs,

                # We reset the counters.
                Core.reset_counters()

                # We clean the output directory.
                self.almost_everything(clean_all)
        else:
            # The list to test is empty.

            # We clean the output directory.
            self.almost_everything(clean_all)

    @classmethod
    def file_to_delete(cls):
        """
        Return the list of file to delete.
        """

        # We initiate the directory we have to look for.
        directory = PyFunceble.OUTPUT_DIRECTORY + PyFunceble.OUTPUTS["parent_directory"]

        if not directory.endswith(PyFunceble.directory_separator):  # pragma: no cover
            # For safety, if it does not ends with the directory separator, we append it
            # to its end.
            directory += PyFunceble.directory_separator

        # We initiate a variable which will save the list of file to delete.
        result = []

        for root, _, files in PyFunceble.walk(directory):
            # We walk in the directory and get all files and sub-directories.

            for file in files:
                # If there is files in the current sub-directory, we loop
                # through the list of files.

                if file not in [".gitignore", ".keep"]:
                    # The file is not into our list of file we do not have to delete.

                    if root.endswith(PyFunceble.directory_separator):
                        # The root ends with the directory separator.

                        # We construct the path and append the full path to the result.
                        result.append(root + file)
                    else:
                        # The root directory does not ends with the directory separator.

                        # We construct the path by appending the directory separator
                        # between the root and the filename and append the full path to
                        # the result.
                        result.append(
                            root + PyFunceble.directory_separator + file
                        )  # pragma: no cover

        # We return our list of file to delete.
        return result

    @classmethod
    def databases_to_delete(cls):  # pragma: no cover
        """
        Set the databases files to delete.
        """

        # We initiate the directory we have to look for.
        directory = PyFunceble.CURRENT_DIRECTORY

        # We initate the result variable.
        result = []

        # We append the dir_structure file.
        result.append(
            directory
            + PyFunceble.CONFIGURATION["outputs"]["default_files"]["dir_structure"]
        )

        # We append the iana file.
        result.append(
            directory + PyFunceble.CONFIGURATION["outputs"]["default_files"]["iana"]
        )

        # We append the public suffix file.
        result.append(
            directory
            + PyFunceble.CONFIGURATION["outputs"]["default_files"]["public_suffix"]
        )

        # We append the inactive database file.
        result.append(
            directory
            + PyFunceble.CONFIGURATION["outputs"]["default_files"]["inactive_db"]
        )

        # We append the mining database file.
        result.append(
            directory + PyFunceble.CONFIGURATION["outputs"]["default_files"]["mining"]
        )

        # We append the whois database file.
        result.append(
            directory + PyFunceble.CONFIGURATION["outputs"]["default_files"]["whois_db"]
        )

        return result

    def almost_everything(self, clean_all=False):
        """
        Delete almost all discovered files.

        :param clean_all:
            Tell the subsystem if we have to clean everything instesd
            of almost everything.
        :type clean_all: bool
        """

        # We get the list of file to delete.
        to_delete = self.file_to_delete()

        if clean_all:  # pragma: no cover
            to_delete.extend(self.databases_to_delete())

        for file in to_delete:
            # We loop through the list of file to delete.

            # And we delete the currently read file.
            File(file).delete()

        if clean_all:  # pragma: no cover
            Load(PyFunceble.CURRENT_DIRECTORY)