from typing import Tuple, List
from types import ModuleType

from docutils import nodes
from docutils.utils import Reporter
from docutils.nodes import Node, system_message
from sphinx.util import logging
from sphinx.util.docutils import SphinxRole, CustomReSTDispatcher
from sphinx.util.typing import RoleFunction
from sphinx.addnodes import download_reference

logger = logging.getLogger(__name__)

# Default values
dft_url = {
    'dokuwiki': 'https://wiki.analog.com',
    'ez': 'https://ez.analog.com',
    'mw': 'https://www.mathworks.com',
    'digikey': 'https://www.digikey.com/scripts/DkSearch/dksus.dll?KeywordSearch?Site=US&Keywords=',
    'mouser': 'https://www.mouser.com/c/?q=',
    'arrow': 'https://www.arrow.com/en/products/search?q=',
    'git_gui': 'https://github.com/analogdevicesinc/{repo}/tree',
    'git_raw': 'https://raw.githubusercontent.com/analogdevicesinc/{repo}',
    'git_other': 'https://github.com/analogdevicesinc/{repo}/{other}',
    'git_down': 'https://analogdevicesinc.github.io/DownGit/#/home?url=',
    'adi': 'https://www.analog.com',
    'xilinx': 'https://www.xilinx.com',
    'intel': 'https://www.intel.com',
}

git_repos = {
    # case insensitive            url_path                     name
    'hdl':                       ['hdl',                       "HDL"],
    'testbenches':               ['testbenches',               "Testbenches"],
    'linux':                     ['linux',                     "Linux"],
    'no-os':                     ['no-OS',                     "no-OS"],
    'libiio':                    ['libiio',                    "libiio"],
    'scopy':                     ['scopy',                     "Scopy"],
    'iio-oscilloscope':          ['iio-oscilloscope',          "IIO Oscilloscope"],
    'doctools':                  ['doctools',                  "Doctools"],
    'documentation':             ['documentation',             "System Level Documentation"],
    'pyadi-iio':                 ['pyadi-iio',                 "PyADI-IIO"],
    'meta-adi':                  ['meta-adi',                  "META-ADI"],
    'wiki-scripts':              ['wiki-scripts',              "Wiki Scripts"],
    'linux_image_adi-scripts':   ['linux_image_ADI-scripts',   "ADI Scripts for Linux images"],
    'highspeedconvertertoolbox': ['HighSpeedConverterToolbox', "High Speed Converter Toolbox"],
    'transceivertoolbox':        ['TransceiverToolbox',        "Transceiver Toolbox"]
}
vendors = ['xilinx', 'intel', 'mw']
suppliers = ['digikey', 'mouser', 'arrow']


def get_url_config(name, config):
    return getattr(config, "url_"+name)

def get_outer_inner(text):
    """
    Extract 'outer <inner>' fields.
    """
    pos = text.find('<')
    if pos != -1 and text[len(text)-1] == '>':
        return (text[0:pos].strip(), text[pos+1:-1])
    else:
        return (None, text)


def color(class_name):
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        node = nodes.inline(text=text, classes=[class_name])
        return [node], []

    return role


def datasheet():
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        # DEPRECATED
        logger.warning("The datasheet role has been deprecated, use the adi role "
                       "instead.",
                       location=(inliner.document.settings.env.docname, lineno))
        return [], []

    return role


def dokuwiki_resolve(config, text, target):
    path = target
    if text is None:
        text = path[path.rfind('/')+1:] if '/' in path else path
    if len(path) > 0 and path[0] == '/':
        path = path[1:]
    url = get_url_config('dokuwiki', config) + '/' + path
    return (url, text)


def dokuwiki():
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        text, path = get_outer_inner(text)
        config = inliner.document.settings.env.app.config
        url, text = dokuwiki_resolve(config, text, path)
        node = nodes.reference(rawtext, text, refuri=url,
                               classes=['icon', 'dokuwiki'], **options)
        return [node], []

    return role


def ez_resolve(config, text, target):
    path = target
    if path == '/':
        path = ''
    url = get_url_config('ez', config) + '/' + path
    if text is None:
        text = "EngineerZone"
    return (url, text)


def ez():
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        text, path = get_outer_inner(text)
        config = inliner.document.settings.env.app.config
        url, text = ez_resolve(config, text, path)
        node = nodes.reference(rawtext, text, refuri=url,
                               classes=['icon', 'ez'], **options)
        return [node], []

    return role


def get_default_brach(repo, repos):
    """
    Get default branch for each repo.
    If it is not known, returns 'main'
    """
    if not repos:
        from ..lut import repos

    if repo in repos:
        return repos[repo]['branch']
    else:
        return 'main'

class GitRoleDispatcher(CustomReSTDispatcher):
    """Custom dispatcher for (down)git role.

    This enables :git-***: and :downgit-***: roles on parsing reST document.
    """
    def role(
        self, role_name: str, language_module: ModuleType, lineno: int, reporter: Reporter,
    ) -> Tuple[RoleFunction, List[system_message]]:
        if len(role_name) > 4 and (role_name.startswith(('git-')) or role_name.startswith(('git+'))):
            return git_role(role_name, False), []
        elif len(role_name) > 8 and (role_name.startswith(('downgit-')) or role_name.startswith(('downgit+'))):
            return git_role(role_name, True), []
        else:
            return super().role(role_name, language_module, lineno, reporter)


class git_role(SphinxRole):
    """
    Create links to git upstream.
    Prefers knowns repositories, but will generate for other repositories
    with a info note
    """
    def __init__(self, orig_name: str, down: bool) -> None:
        self.orig_name = orig_name
        self.down = down

    def run(self) -> Tuple[List[Node], List[system_message]]:
        assert self.name == self.orig_name.lower()
        assert self.name.startswith('downgit' if self.down else 'git')

        app = self.inliner.document.settings.env.app
        repos = app.lut['repos']
        config = app.config
        text, path = get_outer_inner(self.text)
        url, text = self.resolve(config, repos, self.orig_name, text, path, self.down)

        node = nodes.reference(self.rawtext, text, refuri=url,
                               classes=['icon', 'git'], **self.options)
        return [node], []

    @staticmethod
    def resolve(config, repos, name, text, target, down):
        repo_ = name[8 if down else 4:]
        repo = repo_.lower()

        if repo not in git_repos:
            alt_name = repo_
            repo = name[8 if down else 4:]
        else:
            alt_name = git_repos[repo][1]
            repo = git_repos[repo][0]

        pos = target.find('+')
        if target[0:pos] == "raw":
            type_ = "raw"
            target = target[pos+1:]
        elif target[0:pos] == "gui":
            type_ = "gui"
            target = target[pos+1:]
        elif pos == len(target)-1:
            type_ = target[:-1]
            target = ''
        else:
            type_ = "gui"

        if type_ in ['raw', 'gui']:
            pos = target.find(':')
            if pos in [0, -1]:
                branch = get_default_brach(repo, repos)
            else:
                branch = target[0:pos]
            target = target[pos+1:]
            if text is None:
                if target == '/' or target == '':
                    text = "ADI " + alt_name + " repository"
                    if branch != 'main':
                        text += "'s branch " + branch
                else:
                    text = target
            if target == '/':
                target = ''

            down_ = get_url_config('git_down', config) if down else ''
            url = get_url_config('git_'+type_, config).format(repo=repo)
            url = down_ + url + '/' + branch + '/' + target
        else:
            if text is None:
                    text = "ADI " + alt_name + " repository"
                    if type_ != '':
                        text += f" ({type_})"
            url = get_url_config('git_other', config).format(repo=repo, other=type_)

        return (url, text)


def adi_resolve(config, text, target):
    if text is None:
        text = target
    target = '' if target == '/' else target
    url = get_url_config('adi', config) + '/' + target

    return (url, text)

def adi():
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        text, target = get_outer_inner(text)
        config = inliner.document.settings.env.app.config
        url, text = adi_resolve(config, text, target)
        node = nodes.reference(rawtext, text, refuri=url,
                               classes=['icon', 'adi'], **options)
        return [node], []

    return role


def vendor_resolve(config, vendor_name, text, target):
    path = target
    if text is None:
        text = path[path.rfind('/')+1:] if '/' in path else path
    url = get_url_config(vendor_name, config) + '/' + path
    return (url, text)


def vendor(vendor_name):
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        text, path = get_outer_inner(text)
        config = inliner.document.settings.env.app.config
        url, text = vendor_resolve(config, vendor_name, text, path)
        node = nodes.reference(rawtext, text, refuri=url, **options)
        return [node], []

    return role


def supplier_resolve(config, supplier_name, text, target):
    path = target
    if text is None:
        text = path
    url = get_url_config(supplier_name, config) + path
    return (url, text)


def supplier(supplier_name):
    def role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        text, path = get_outer_inner(text)
        config = inliner.document.settings.env.app.config
        url, text = supplier_resolve(config, supplier_name, text, path)
        node = nodes.reference(rawtext, text, refuri=url, **options)
        return [node], []

    return role


def install_dispatcher(app, docname: str, source: List[str]) -> None:
    """Enable GitRoleDispatcher.

    .. note:: The installed dispatcher will be uninstalled on disabling sphinx_domain
              automatically.
    """
    dispatcher = GitRoleDispatcher()
    dispatcher.enable()


def links_target_blank(app, doctree, fromdocname):
    for node in doctree.traverse(nodes.reference):
        if ('refuri' in node and
            node['refuri'].startswith(('http://', 'https://'))):
            node['target'] = '_blank'
            if isinstance(node, download_reference):
                node['classes'].append('icon')
            elif 'icon' not in node['classes']:
                node['classes'].extend(('icon', 'link'))


def common_setup(app):
    app.add_role("red",                 color('red'))
    app.add_role("green",               color('green'))
    app.add_role("datasheet",           datasheet())
    app.add_role("ez",                  ez())
    app.add_role("adi",                 adi())
    app.add_role("dokuwiki",            dokuwiki())
    app.add_role("dokuwiki+deprecated", dokuwiki())
    for name in vendors:
        app.add_role(name, vendor(name))
    for name in suppliers:
        app.add_role(name, supplier(name))

    for key in dft_url:
        app.add_config_value('url_' + key, dft_url[key], 'env')

    app.connect('source-read', install_dispatcher)
    app.connect('doctree-resolved', links_target_blank)
