# pylint: disable=C0103,W0622
"""
Sphinx documentation for Salt
"""
import os
import re
import sys
import time
import types

from sphinx.directives.other import TocTree


class Mock:
    """
    Mock out specified imports.

    This allows autodoc to do its thing without having oodles of req'd
    installed libs. This doesn't work with ``import *`` imports.

    This Mock class can be configured to return a specific values at specific names, if required.

    https://read-the-docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
    """

    def __init__(
        self, mapping=None, *args, **kwargs
    ):  # pylint: disable=unused-argument
        """
        Mapping allows autodoc to bypass the Mock object, but actually assign
        a specific value, expected by a specific attribute returned.
        """
        self.__mapping = mapping or {}

    __all__ = []

    def __call__(self, *args, **kwargs):
        # If mocked function is used as a decorator, expose decorated function.
        # if args and callable(args[-1]):
        #     functools.update_wrapper(ret, args[0])
        return Mock(mapping=self.__mapping)

    def __getattr__(self, name):
        if name in self.__mapping:
            data = self.__mapping.get(name)
        elif name in ("__file__", "__path__"):
            data = "/dev/null"
        elif name in ("__mro_entries__", "__qualname__"):
            raise AttributeError("'Mock' object has no attribute '%s'" % (name))
        else:
            data = Mock(mapping=self.__mapping)
        return data

    def __iter__(self):
        return self

    @staticmethod
    def __next__():
        raise StopIteration

    # For Python 2
    next = __next__


def mock_decorator_with_params(*oargs, **okwargs):  # pylint: disable=unused-argument
    """
    Optionally mock a decorator that takes parameters

    E.g.:

    @blah(stuff=True)
    def things():
        pass
    """

    def inner(fn, *iargs, **ikwargs):  # pylint: disable=unused-argument
        if hasattr(fn, "__call__"):
            return fn
        return Mock()

    return inner


MOCK_MODULES = [
    # Python stdlib
    "user",
    # salt core
    "Crypto",
    "Crypto.Signature",
    "Crypto.Cipher",
    "Crypto.Hash",
    "Crypto.PublicKey",
    "Crypto.Random",
    "Crypto.Signature",
    "Crypto.Signature.PKCS1_v1_5",
    "distro",
    "M2Crypto",
    "msgpack",
    "yaml",
    "yaml.constructor",
    "yaml.nodes",
    "yaml.parser",
    "yaml.scanner",
    "zmq",
    "zmq.eventloop",
    "zmq.eventloop.ioloop",
    # third-party libs for cloud modules
    "libcloud",
    "libcloud.compute",
    "libcloud.compute.base",
    "libcloud.compute.deployment",
    "libcloud.compute.providers",
    "libcloud.compute.types",
    "libcloud.loadbalancer",
    "libcloud.loadbalancer.types",
    "libcloud.loadbalancer.providers",
    "libcloud.common",
    "libcloud.common.google",
    # third-party libs for netapi modules
    "cherrypy",
    "cherrypy.lib",
    "cherrypy.process",
    "cherrypy.wsgiserver",
    "cherrypy.wsgiserver.ssl_builtin",
    "tornado",
    "tornado.concurrent",
    "tornado.escape",
    "tornado.gen",
    "tornado.httpclient",
    "tornado.httpserver",
    "tornado.httputil",
    "tornado.ioloop",
    "tornado.iostream",
    "tornado.netutil",
    "tornado.simple_httpclient",
    "tornado.stack_context",
    "tornado.web",
    "tornado.websocket",
    "tornado.locks",
    "ws4py",
    "ws4py.server",
    "ws4py.server.cherrypyserver",
    "ws4py.websocket",
    # modules, renderers, states, returners, et al
    "ClusterShell",
    "ClusterShell.NodeSet",
    "MySQLdb",
    "MySQLdb.cursors",
    "OpenSSL",
    "avahi",
    "boto.regioninfo",
    "dbus",
    "django",
    "dns",
    "dns.resolver",
    "dson",
    "hjson",
    "jnpr",
    "jnpr.junos",
    "jnpr.junos.utils",
    "jnpr.junos.utils.config",
    "jnpr.junos.utils.sw",
    "keyring",
    "kubernetes",
    "kubernetes.config",
    "libvirt",
    "lxml",
    "lxml.etree",
    "msgpack",
    "nagios_json",
    "napalm",
    "netaddr",
    "netaddr.IPAddress",
    "netaddr.core",
    "netaddr.core.AddrFormatError",
    "ntsecuritycon",
    "psutil",
    "pycassa",
    "pyconnman",
    "pyiface",
    "pymongo",
    "pyroute2",
    "pyroute2.ipdb",
    "rabbitmq_server",
    "redis",
    "rpm",
    "rpmUtils",
    "rpmUtils.arch",
    "salt.ext.six.moves.winreg",
    "twisted",
    "twisted.internet",
    "twisted.internet.protocol",
    "twisted.internet.protocol.DatagramProtocol",
    "win32security",
    "yum",
    "zfs",
]

MOCK_MODULES_MAPPING = {
    "cherrypy": {"config": mock_decorator_with_params},
    "ntsecuritycon": {"STANDARD_RIGHTS_REQUIRED": 0, "SYNCHRONIZE": 0,},
    "psutil": {"total": 0},  # Otherwise it will crash Sphinx
}

for mod_name in MOCK_MODULES:
    sys.modules[mod_name] = Mock(mapping=MOCK_MODULES_MAPPING.get(mod_name))

# Define a fake version attribute for the following libs.
sys.modules["libcloud"].__version__ = "0.0.0"
sys.modules["msgpack"].version = (1, 0, 0)
sys.modules["psutil"].version_info = (3, 0, 0)
sys.modules["pymongo"].version = "0.0.0"
sys.modules["tornado"].version_info = (0, 0, 0)
sys.modules["boto.regioninfo"]._load_json_file = {"endpoints": None}


# -- Add paths to PYTHONPATH ---------------------------------------------------
try:
    docs_basepath = os.path.abspath(os.path.dirname(__file__))
except NameError:
    # sphinx-intl and six execute some code which will raise this NameError
    # assume we're in the doc/ directory
    docs_basepath = os.path.abspath(os.path.dirname("."))

addtl_paths = (
    os.pardir,  # salt itself (for autodoc)
    "_ext",  # custom Sphinx extensions
)

for addtl_path in addtl_paths:
    sys.path.insert(0, os.path.abspath(os.path.join(docs_basepath, addtl_path)))

# We're now able to import salt
import salt.version  # isort:skip

formulas_dir = os.path.join(os.pardir, docs_basepath, "formulas")

# ----- Intersphinx Settings ------------------------------------------------>
intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
# <---- Intersphinx Settings -------------------------------------------------

# -- General Configuration -----------------------------------------------------

# Set a var if we're building docs for the live site or not
on_saltstack = "SALT_ON_SALTSTACK" in os.environ

project = "Salt"
repo_primary_branch = (
    "master"  # This is the default branch on GitHub for the Salt project
)
version = salt.version.__version__
latest_release = os.environ.get(
    "LATEST_RELEASE", "latest_release"
)  # latest release (2019.2.3)
previous_release = os.environ.get(
    "PREVIOUS_RELEASE", "previous_release"
)  # latest release from previous branch (2018.3.5)
previous_release_dir = os.environ.get(
    "PREVIOUS_RELEASE_DIR", "previous_release_dir"
)  # path on web server for previous branch (2018.3)
next_release = ""  # next release
next_release_dir = ""  # path on web server for next release branch

today = ""
copyright = ""
if on_saltstack:
    today = (
        "Generated on "
        + time.strftime("%B %d, %Y")
        + " at "
        + time.strftime("%X %Z")
        + "."
    )
    copyright = time.strftime("%Y")

# < --- START do not merge these settings to other branches START ---> #
build_type = os.environ.get(
    "BUILD_TYPE", repo_primary_branch
)  # latest, previous, master, next
# < --- END do not merge these settings to other branches END ---> #

# Set google custom search engine

if build_type == repo_primary_branch:
    release = latest_release
    search_cx = "011515552685726825874:v1had6i279q"  # master
    # search_cx = '011515552685726825874:x17j5zl74g8' # develop
elif build_type == "next":
    release = next_release
    search_cx = "011515552685726825874:ht0p8miksrm"  # latest
elif build_type == "previous":
    release = previous_release
    if release.startswith("3003"):
        search_cx = "a70a1a73eef62aecd"  # 3003
    elif release.startswith("3002"):
        search_cx = "5026f4f2af0bdbe2d"  # 3002
    elif release.startswith("3001"):
        search_cx = "f0e4f298fa32b8a5e"  # 3001
    elif release.startswith("3000"):
        search_cx = "011515552685726825874:3skhaozjtyn"  # 3000
    elif release.startswith("2019.2"):
        search_cx = "011515552685726825874:huvjhlpptnm"  # 2019.2
    elif release.startswith("2018.3"):
        search_cx = "011515552685726825874:vadptdpvyyu"  # 2018.3
    elif release.startswith("2017.7"):
        search_cx = "011515552685726825874:w-hxmnbcpou"  # 2017.7
    elif release.startswith("2016.11"):
        search_cx = "011515552685726825874:dlsj745pvhq"  # 2016.11
    else:
        search_cx = "011515552685726825874:ht0p8miksrm"  # latest
else:  # latest or something else
    release = latest_release
    search_cx = "011515552685726825874:ht0p8miksrm"  # latest

needs_sphinx = "1.3"

spelling_lang = "en_US"
language = "en"
locale_dirs = [
    "_locale",
]

master_doc = "contents"
templates_path = ["_templates"]
exclude_patterns = ["_build", "_incl/*", "ref/cli/_includes/*.rst"]

extensions = [
    "saltdomain",  # Must come early
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx.ext.autosummary",
    "sphinx.ext.extlinks",
    "sphinx.ext.intersphinx",
    "httpdomain",
    "youtube",
    "saltrepo"
    #'saltautodoc', # Must be AFTER autodoc
    #'shorturls',
]

try:
    import sphinxcontrib.spelling  # false positive, pylint: disable=unused-import
except ImportError:
    pass
else:
    extensions += ["sphinxcontrib.spelling"]

modindex_common_prefix = ["salt."]

autosummary_generate = True
autosummary_generate_overwrite = False

# strip git rev as there won't necessarily be a release based on it
stripped_release = re.sub(r"-\d+-g[0-9a-f]+$", "", release)

# Define a substitution for linking to the latest release tarball
rst_prolog = """\
.. |current_release_doc| replace:: :doc:`/topics/releases/{release}`
.. |saltrepo| replace:: https://github.com/saltstack/salt
.. _`salt-users`: https://groups.google.com/forum/#!forum/salt-users
.. _`salt-announce`: https://groups.google.com/forum/#!forum/salt-announce
.. _`salt-packagers`: https://groups.google.com/forum/#!forum/salt-packagers
.. _`salt-slack`: https://saltstackcommunity.herokuapp.com/
.. |windownload| raw:: html

     <p>Python3 x86: <a
     href="https://repo.saltstack.com/windows/Salt-Minion-{release}-Py3-x86-Setup.exe"><strong>Salt-Minion-{release}-x86-Setup.exe</strong></a>
      | <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-Py3-x86-Setup.exe.md5"><strong>md5</strong></a></p>

     <p>Python3 AMD64: <a
     href="https://repo.saltstack.com/windows/Salt-Minion-{release}-Py3-AMD64-Setup.exe"><strong>Salt-Minion-{release}-AMD64-Setup.exe</strong></a>
      | <a href="https://repo.saltstack.com/windows/Salt-Minion-{release}-Py3-AMD64-Setup.exe.md5"><strong>md5</strong></a></p>

.. |osxdownloadpy3| raw:: html

     <p>x86_64: <a href="https://repo.saltstack.com/osx/salt-{release}-py3-x86_64.pkg"><strong>salt-{release}-py3-x86_64.pkg</strong></a>
      | <a href="https://repo.saltstack.com/osx/salt-{release}-py3-x86_64.pkg.md5"><strong>md5</strong></a></p>

""".format(
    release=stripped_release
)

# A shortcut for linking to tickets on the GitHub issue tracker
extlinks = {
    "blob": (
        "https://github.com/saltstack/salt/blob/%s/%%s" % repo_primary_branch,
        None,
    ),
    "issue": ("https://github.com/saltstack/salt/issues/%s", "issue #"),
    "pull": ("https://github.com/saltstack/salt/pull/%s", "PR #"),
    "formula_url": ("https://github.com/saltstack-formulas/%s", ""),
}


# ----- Localization -------------------------------------------------------->
locale_dirs = ["locale/"]
gettext_compact = False
# <---- Localization ---------------------------------------------------------


### HTML options
# set 'HTML_THEME=saltstack' to use previous theme
html_theme = os.environ.get("HTML_THEME", "saltstack2")
html_theme_path = ["_themes"]
html_title = ""
html_short_title = "Salt"

html_static_path = ["_static"]
html_logo = None  # specified in the theme layout.html
html_favicon = "favicon.ico"
smartquotes = False

# Use Google customized search or use Sphinx built-in JavaScript search
if on_saltstack:
    html_search_template = "googlesearch.html"
else:
    html_search_template = "searchbox.html"

html_additional_pages = {
    "404": "404.html",
}

html_default_sidebars = [
    html_search_template,
    "version.html",
    "localtoc.html",
    "relations.html",
    "sourcelink.html",
    "saltstack.html",
]
html_sidebars = {
    "ref/**/all/salt.*": [
        html_search_template,
        "version.html",
        "modules-sidebar.html",
        "localtoc.html",
        "relations.html",
        "sourcelink.html",
        "saltstack.html",
    ],
    "ref/formula/all/*": [],
}

html_context = {
    "on_saltstack": on_saltstack,
    "html_default_sidebars": html_default_sidebars,
    "github_base": "https://github.com/saltstack/salt",
    "github_issues": "https://github.com/saltstack/salt/issues",
    "github_downloads": "https://github.com/saltstack/salt/downloads",
    "latest_release": latest_release,
    "previous_release": previous_release,
    "previous_release_dir": previous_release_dir,
    "next_release": next_release,
    "next_release_dir": next_release_dir,
    "search_cx": search_cx,
    "build_type": build_type,
    "today": today,
    "copyright": copyright,
    "repo_primary_branch": repo_primary_branch,
}

html_use_index = True
html_last_updated_fmt = "%b %d, %Y"
html_show_sourcelink = False
html_show_sphinx = True
html_show_copyright = True

### Latex options

latex_documents = [
    ("contents", "Salt.tex", "Salt Documentation", "SaltStack, Inc.", "manual"),
]

latex_logo = "_static/salt-logo.png"

latex_elements = {
    "inputenc": "",  # use XeTeX instead of the inputenc LaTeX package.
    "utf8extra": "",
    "preamble": r"""
    \usepackage{fontspec}
    \setsansfont{Linux Biolinum O}
    \setromanfont{Linux Libertine O}
    \setmonofont{Source Code Pro}
""",
}
### Linux Biolinum, Linux Libertine: http://www.linuxlibertine.org/
### Source Code Pro: https://github.com/adobe-fonts/source-code-pro/releases


### Linkcheck options
linkcheck_ignore = [
    r"http://127.0.0.1",
    r"http://salt:\d+",
    r"http://local:\d+",
    r"https://console.aws.amazon.com",
    r"http://192.168.33.10",
    r"http://domain:\d+",
    r"http://123.456.789.012:\d+",
    r"http://localhost",
    r"https://groups.google.com/forum/#!forum/salt-users",
    r"https://www.elastic.co/logstash/docs/latest/inputs/udp",
    r"https://www.elastic.co/logstash/docs/latest/inputs/zeromq",
    r"http://www.youtube.com/saltstack",
    r"https://raven.readthedocs.io",
    r"https://getsentry.com",
    r"https://salt-cloud.readthedocs.io",
    r"https://salt.readthedocs.io",
    r"http://www.pip-installer.org/",
    r"http://www.windowsazure.com/",
    r"https://github.com/watching",
    r"dash-feed://",
    r"https://github.com/saltstack/salt/",
    r"http://bootstrap.saltstack.org",
    r"https://bootstrap.saltstack.com",
    r"https://raw.githubusercontent.com/saltstack/salt-bootstrap/stable/bootstrap-salt.sh",
    r"media.readthedocs.org/dash/salt/latest/salt.xml",
    r"https://portal.aws.amazon.com/gp/aws/securityCredentials",
    r"https://help.github.com/articles/fork-a-repo",
    r"dash-feed://https%3A//media.readthedocs.org/dash/salt/latest/salt.xml",
]

linkcheck_anchors = False

### Manpage options
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
authors = [
    "Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file",
]

man_pages = [
    ("contents", "salt", "Salt Documentation", authors, 7),
    ("ref/cli/salt", "salt", "salt", authors, 1),
    ("ref/cli/salt-master", "salt-master", "salt-master Documentation", authors, 1),
    ("ref/cli/salt-minion", "salt-minion", "salt-minion Documentation", authors, 1),
    ("ref/cli/salt-key", "salt-key", "salt-key Documentation", authors, 1),
    ("ref/cli/salt-cp", "salt-cp", "salt-cp Documentation", authors, 1),
    ("ref/cli/salt-call", "salt-call", "salt-call Documentation", authors, 1),
    ("ref/cli/salt-proxy", "salt-proxy", "salt-proxy Documentation", authors, 1),
    ("ref/cli/salt-syndic", "salt-syndic", "salt-syndic Documentation", authors, 1),
    ("ref/cli/salt-run", "salt-run", "salt-run Documentation", authors, 1),
    ("ref/cli/salt-ssh", "salt-ssh", "salt-ssh Documentation", authors, 1),
    ("ref/cli/salt-cloud", "salt-cloud", "Salt Cloud Command", authors, 1),
    ("ref/cli/salt-api", "salt-api", "salt-api Command", authors, 1),
    ("ref/cli/salt-unity", "salt-unity", "salt-unity Command", authors, 1),
    ("ref/cli/spm", "spm", "Salt Package Manager Command", authors, 1),
]


### epub options
epub_title = "Salt Documentation"
epub_author = "SaltStack, Inc."
epub_publisher = epub_author
epub_copyright = copyright

epub_scheme = "URL"
epub_identifier = "http://saltstack.com/"

epub_tocdup = False
# epub_tocdepth = 3


def skip_mod_init_member(app, what, name, obj, skip, options):
    # pylint: disable=too-many-arguments,unused-argument
    if name.startswith("_"):
        return True
    if isinstance(obj, types.FunctionType) and obj.__name__ == "mod_init":
        return True
    return False


def _normalize_version(args):
    _, path = args
    return ".".join([x.zfill(4) for x in (path.split("/")[-1].split("."))])


class ReleasesTree(TocTree):
    option_spec = dict(TocTree.option_spec)

    def run(self):
        rst = super().run()
        entries = rst[0][0]["entries"][:]
        entries.sort(key=_normalize_version, reverse=True)
        rst[0][0]["entries"][:] = entries
        return rst


def setup(app):
    app.add_directive("releasestree", ReleasesTree)
    app.connect("autodoc-skip-member", skip_mod_init_member)
