import functools
import os.path

from robot.api.deco import library

__version__ = "N/A"  # This should be ignored when version is parsed


class NameLibrary:  # Old-style class on purpose!
    handler_count = 10

    def simple1(self):
        """Simple 1"""

    def simple2___(self):
        """Simple 2"""

    def underscore_name(self):
        """Underscore Name"""

    def underscore_name2_(self):
        """Underscore Name2"""

    def un_der__sco_r__e_3(self):
        """Un Der Sco R E 3"""

    def MiXeD_CAPS(self):
        """MiXeD CAPS"""

    def camelCase(self):
        """Camel Case"""

    def camelCase2(self):
        """Camel Case 2"""

    def mixedCAPSCamel(self):
        """Mixed CAPS Camel"""

    def camelCase_and_underscores_doesNot_work(self):
        """CamelCase And Underscores DoesNot Work"""

    def _skipped1(self):
        """Starts with underscore"""

    skipped2 = "Not a function"


class DocLibrary:
    handler_count = 4

    def no_doc(self):
        pass

    no_doc.expected_doc = ""
    no_doc.expected_shortdoc = ""

    def one_line_doc(self):
        """One line doc"""

    one_line_doc.expected_doc = "One line doc"
    one_line_doc.expected_shortdoc = "One line doc"

    def multiline_doc(self):
        """First line is short doc.

        Full doc spans
        multiple lines.
        """

    multiline_doc.expected_doc = (
        "First line is short doc.\n\nFull doc spans\nmultiple lines."
    )
    multiline_doc.expected_shortdoc = "First line is short doc."

    def multiline_doc_with_split_short_doc(self):
        """Short doc can be split into
        multiple
        physical
        lines.

        This is documentation body and not included
        in short doc.

        Still body.
        """

    multiline_doc_with_split_short_doc.expected_doc = """\
Short doc can be split into
multiple
physical
lines.

This is documentation body and not included
in short doc.

Still body."""
    multiline_doc_with_split_short_doc.expected_shortdoc = """\
Short doc can be split into
multiple
physical
lines."""


class ArgInfoLibrary:
    handler_count = 13

    def no_args(self):
        """(), {}, None, None"""
        # Argument inspection had a bug when there was args on function body
        # so better keep some of them around here.
        a = b = c = 1
        print(a, b, c)

    def required1(self, one):
        """('one',), {}, None, None"""

    def required2(self, one, two):
        """('one', 'two'), {}, None, None"""

    def required9(self, one, two, three, four, five, six, seven, eight, nine):
        """('one','two','three','four','five','six','seven','eight','nine'), \
           {}, None, None"""

    def default1(self, one=1):
        """('one',), {'one': 1}, None, None"""

    def default5(self, one="", two=None, three=3, four="huh", five=True):
        """('one', 'two', 'three', 'four', 'five'), \
           {'one': '', 'two': None, 'three': 3, 'four': 'huh', 'five': True}, \
           None, None"""

    def required1_default1(self, one, two=""):
        """('one', 'two'), {'two': ''}, None, None"""

    def required2_default3(self, one, two, three=3, four=4, five=5):
        """('one', 'two', 'three', 'four', 'five'), \
           {'three': 3, 'four': 4, 'five': 5}, None, None"""

    def varargs(self, *one):
        """(), {}, 'one', None"""

    def required2_varargs(self, one, two, *three):
        """('one', 'two'), {}, 'three', None"""

    def req4_def2_varargs(self, one, two, three, four, five=5, six=6, *seven):
        """('one', 'two', 'three', 'four', 'five', 'six'), \
           {'five': 5, 'six': 6}, 'seven', None"""

    def req2_def3_varargs_kwargs(
        self, three, four, five=5, six=6, seven=7, *eight, **nine
    ):
        """('three', 'four', 'five', 'six', 'seven'), \
           {'five': 5, 'six': 6, 'seven': 7}, 'eight', 'nine'"""

    def varargs_kwargs(self, *one, **two):
        """(), {}, 'one', 'two'"""


class GetattrLibrary:
    handler_count = 3
    keyword_names = ["foo", "bar", "zap"]

    def get_keyword_names(self):
        return self.keyword_names

    def __getattr__(self, name):
        def handler(*args):
            return name, args

        if name not in self.keyword_names:
            raise AttributeError
        return handler


class SynonymLibrary:
    handler_count = 3

    def handler(self):
        pass

    synonym_handler = handler
    another_synonym = handler


@library(auto_keywords=True)
class VersionLibrary:
    ROBOT_LIBRARY_VERSION = "0.1"
    ROBOT_LIBRARY_DOC_FORMAT = "html"
    kw = lambda x: None


class VersionObjectLibrary:

    class _Version:
        def __init__(self, ver):
            self._ver = ver

        def __str__(self):
            return self._ver

    ROBOT_LIBRARY_VERSION = _Version("ver")
    kw = lambda x: None


class RecordingLibrary:
    ROBOT_LIBRARY_SCOPE = "GLOBAL"

    def __init__(self):
        self.kw_accessed = 0
        self.kw_called = 0

    def kw(self):
        self.kw_called += 1

    def __getattribute__(self, name):
        if name == "kw":
            self.kw_accessed += 1
        return object.__getattribute__(self, name)


class ArgDocDynamicLibrary:

    def __init__(self):
        kws = [
            ("No Arg", [], None),
            ("One Arg", ["arg"], None),
            ("One or Two Args", ["arg", "darg=dvalue"], None),
            ("Default as tuple", [("arg",), ("d1", False), ("d2", None)], None),
            ("Many Args", ["*args"], None),
            ("No Arg Spec", None, None),
            ("Multiline", None, "Multiline\nshort doc!\n\nBody\nhere."),
        ]
        self._keywords = {
            name: _KeywordInfo(name, argspec, doc) for name, argspec, doc in kws
        }

    def get_keyword_names(self):
        return sorted(self._keywords)

    def run_keyword(self, name, args):
        print(f'*INFO* Executed keyword "{name}" with arguments {args}.')

    def get_keyword_documentation(self, name):
        if name in ("__init__", "__intro__"):
            raise ValueError(f"'{name}' should be used only with Libdoc'")
        try:
            return self._keywords[name].doc
        except KeyError:
            raise ValueError(f"'{name}' not found")

    def get_keyword_arguments(self, name):
        return self._keywords[name].argspec


class ArgDocDynamicLibraryWithKwargsSupport(ArgDocDynamicLibrary):

    def __init__(self):
        ArgDocDynamicLibrary.__init__(self)
        for name, argspec in [
            ("Kwargs", ["**kwargs"]),
            ("Varargs and Kwargs", ["*args", "**kwargs"]),
        ]:
            self._keywords[name] = _KeywordInfo(name, argspec)

    def run_keyword(self, name, args, kwargs={}):
        argstr = " ".join(
            [str(a) for a in args] + [f"{k}:{kwargs[k]}" for k in sorted(kwargs)]
        )
        print(f"*INFO* Executed keyword {name} with arguments {argstr}")


class DynamicWithSource:
    path = os.path.normpath(os.path.dirname(__file__) + "/classes.py")
    keywords = {
        "only path": path,
        "path & lineno": path + ":42",
        "lineno only": ":6475",
        "invalid path": "path validity is not validated",
        "path w/ colon": r"c:\temp\lib.py",
        "path w/ colon & lineno": r"c:\temp\lib.py:1234567890",
        "no source": None,
        "nön-äscii": "hyvä esimerkki",
        "nön-äscii utf-8": b"\xe7\xa6\x8f:88",
        "invalid source": 666,
    }

    def get_keyword_names(self):
        return list(self.keywords)

    def run_keyword(self, name, args, kws):
        pass

    def get_keyword_source(self, name):
        return self.keywords[name]


class _KeywordInfo:
    doc_template = "Keyword documentation for {}"

    def __init__(self, name, argspec, doc=None):
        self.doc = doc or self.doc_template.format(name)
        self.argspec = argspec


class InvalidGetDocDynamicLibrary(ArgDocDynamicLibrary):

    def get_keyword_documentation(self, name, invalid_arg):
        pass


class InvalidGetArgsDynamicLibrary(ArgDocDynamicLibrary):

    def get_keyword_arguments(self, name):
        1 / 0


class InvalidAttributeDynamicLibrary(ArgDocDynamicLibrary):
    get_keyword_documentation = True
    get_keyword_arguments = False


def noop(x):
    return x


def wraps(x):
    @functools.wraps(x)
    def wrapper(*a, **k):
        return x(*a, **k)

    return wrapper


@noop
@noop
@functools.total_ordering
class Decorated:

    @noop
    def no_wrapper(self):
        pass

    @noop
    @wraps
    @noop
    @wraps
    def wrapper(self):
        pass

    if hasattr(functools, "lru_cache"):

        @functools.lru_cache()
        def external(self):
            pass

    no_def = lambda self: None

    def __eq__(self, other):
        return self is other

    def __lt__(self, other):
        return True


NoClassDefinition = type("NoClassDefinition", (), {})
